Running a node

The main function in the forum.go file is responsible for running the Forum Application blockchain.

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

	"github.com/spf13/viper"

	"github.com/cometbft/cometbft/abci/tutorials/abci-v2-forum-app/abci"
	cfg "github.com/cometbft/cometbft/config"
	cmtflags "github.com/cometbft/cometbft/libs/cli/flags"
	cmtlog "github.com/cometbft/cometbft/libs/log"
	nm "github.com/cometbft/cometbft/node"
	"github.com/cometbft/cometbft/p2p"
	"github.com/cometbft/cometbft/privval"
	"github.com/cometbft/cometbft/proxy"
)

var homeDir string

func init() {
	flag.StringVar(&homeDir, "home", "", "Path to the CometBFT config directory (if empty, uses $HOME/.forumapp)")
}

func main() {
	flag.Parse()
	if homeDir == "" {
		homeDir = os.ExpandEnv("$HOME/.forumapp")
	}

	config := cfg.DefaultConfig()
	config.SetRoot(homeDir)
	viper.SetConfigFile(fmt.Sprintf("%s/%s", homeDir, "config/config.toml"))

	if err := viper.ReadInConfig(); err != nil {
		log.Fatalf("failed to read config: %v", err)
	}

	logger := cmtlog.NewTMLogger(cmtlog.NewSyncWriter(os.Stdout))
	logger, err := cmtflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel)
	if err != nil {
		log.Printf("failed to parse log level: %v", err)
		defer os.Exit(1)
	}

	dbPath := filepath.Join(homeDir, "forum-db")
	appConfigPath := "app.toml"
	app, err := abci.NewForumApp(dbPath, appConfigPath, logger)
	if err != nil {
		log.Printf("failed to create Forum Application: %v\n", err)
		os.Exit(1)
	}

	nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
	if err != nil {
		log.Printf("failed to load node key: %v", err)
		os.Exit(1)
	}

	pv := privval.LoadFilePV(
		config.PrivValidatorKeyFile(),
		config.PrivValidatorStateFile(),
	)

	node, err := nm.NewNode(
		context.Background(),
		config,
		pv,
		nodeKey,
		proxy.NewLocalClientCreator(app),
		nm.DefaultGenesisDocProviderFunc(config),
		cfg.DefaultDBProvider,
		nm.DefaultMetricsProvider(config.Instrumentation),
		logger,
	)
	if err != nil {
		log.Printf("failed to create CometBFT node")
		os.Exit(1)
	}

	if err := node.Start(); err != nil {
		log.Printf("failed to start CometBFT node")
		os.Exit(1)
	}
	defer func() {
		_ = node.Stop()
		node.Wait()
	}()

	httpAddr := "127.0.0.1:8080"

	server := &http.Server{
		Addr:              httpAddr,
		ReadHeaderTimeout: 5 * time.Second,
	}

	if err := server.ListenAndServe(); err != nil {
		log.Printf("failed to start HTTP server: %v", err)
		os.Exit(1)
	}

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
	<-sigCh

	log.Println("Forum application stopped")
}

Explanation of code

The program begins by parsing command-line flags using flag.Parse(). It then checks if the homeDir variable is empty and assigns a default value if it is.

Next, it creates a configuration object using cfg.DefaultConfig() and sets the root directory using config.SetRoot(homeDir). It also sets the configuration file path using viper.SetConfigFile(fmt.Sprintf("%s/%s", homeDir, "config.toml")). The program attempts to read the configuration file using viper.ReadInConfig(), and if there is an error, it logs the failure and exits.

It then creates a database using db.NewGoLevelDB(filepath.Join(homeDir, "forum-db"), "."). If there is an error during the creation of the database, it logs the failure and exits.

The program proceeds to create an instance of the ForumApp object using forum.NewForumApp(dbPath, appConfigPath). If there is an error during the creation of the ForumApp instance, it logs the failure and exits.

The program then sets up logging using cmtlog.NewTMLogger(cmtlog.NewSyncWriter(os.Stdout)) and parses the log level using cmtflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel). If there is an error during log level parsing, it logs the failure and exits.

The program loads the node key using p2p.LoadNodeKey(config.NodeKeyFile()). If there is an error during the loading of the node key, it logs the failure and exits.

Next, it loads the private validator using privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()).

The program creates a CometBFT node using nm.NewNode() and passes various parameters such as configuration, private validator, node key, client creator, genesis doc provider, database provider, metrics provider, and logger. If there is an error during the creation of the CometBFT node, it logs the failure and exits.

The program starts the CometBFT node using node.Start(). If there is an error during node startup, it logs the failure and exits.

If there is an error starting the HTTP server, it logs the failure and exits.

The program sets up a signal channel to handle SIGINT and SIGTERM signals. It waits for a signal to be received on the channel, and when it does, it stops the CometBFT node using node.Stop() and waits for it to terminate using node.Wait().

Finally, it prints a message indicating that the forum application has stopped.


In the next session, you will learn about Vote Extension that lets validators extend their vote in the forum application blockchain.

Decorative Orb