2018-02-09 16:08:50 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2019-02-19 11:48:48 +00:00
|
|
|
"context"
|
2022-01-12 20:21:09 +00:00
|
|
|
"errors"
|
2018-03-14 09:36:59 +00:00
|
|
|
"fmt"
|
2018-03-23 20:36:59 +00:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
2022-08-24 08:43:21 +00:00
|
|
|
"syscall"
|
2022-07-26 19:41:52 +00:00
|
|
|
"time"
|
2018-02-09 16:08:50 +00:00
|
|
|
|
2022-08-05 10:32:37 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
2020-06-17 18:13:37 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/cli/options"
|
2020-03-25 15:30:21 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
2022-07-26 18:36:37 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
2022-01-12 20:04:07 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/consensus"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
2020-11-24 08:36:09 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
2022-01-13 23:09:16 +00:00
|
|
|
corestate "github.com/nspcc-dev/neo-go/pkg/core/stateroot"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
2022-01-12 20:21:09 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/network"
|
2022-07-21 20:38:23 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/services/metrics"
|
2022-01-12 20:21:09 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/services/notary"
|
2022-01-12 02:01:34 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
2022-07-21 20:38:23 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/services/rpcsrv"
|
2022-01-12 18:09:37 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/services/stateroot"
|
2018-02-09 16:08:50 +00:00
|
|
|
"github.com/urfave/cli"
|
2019-12-30 07:43:05 +00:00
|
|
|
"go.uber.org/zap"
|
2022-12-05 12:43:55 +00:00
|
|
|
"go.uber.org/zap/zapcore"
|
2022-02-07 15:51:54 +00:00
|
|
|
)
|
2022-02-07 10:36:42 +00:00
|
|
|
|
2019-10-19 20:58:45 +00:00
|
|
|
// NewCommands returns 'node' command.
|
|
|
|
func NewCommands() []cli.Command {
|
2023-05-12 13:53:57 +00:00
|
|
|
cfgFlags := []cli.Flag{options.Config, options.ConfigFile}
|
2020-06-17 18:13:37 +00:00
|
|
|
cfgFlags = append(cfgFlags, options.Network...)
|
2019-10-21 05:41:05 +00:00
|
|
|
var cfgWithCountFlags = make([]cli.Flag, len(cfgFlags))
|
|
|
|
copy(cfgWithCountFlags, cfgFlags)
|
2022-10-03 12:05:40 +00:00
|
|
|
cfgFlags = append(cfgFlags, options.Debug)
|
2022-08-05 12:59:23 +00:00
|
|
|
|
2019-10-21 05:41:05 +00:00
|
|
|
cfgWithCountFlags = append(cfgWithCountFlags,
|
|
|
|
cli.UintFlag{
|
|
|
|
Name: "count, c",
|
|
|
|
Usage: "number of blocks to be processed (default or 0: all chain)",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
var cfgCountOutFlags = make([]cli.Flag, len(cfgWithCountFlags))
|
|
|
|
copy(cfgCountOutFlags, cfgWithCountFlags)
|
2019-12-27 09:11:57 +00:00
|
|
|
cfgCountOutFlags = append(cfgCountOutFlags,
|
|
|
|
cli.UintFlag{
|
|
|
|
Name: "start, s",
|
|
|
|
Usage: "block number to start from (default: 0)",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "out, o",
|
|
|
|
Usage: "Output file (stdout if not given)",
|
|
|
|
},
|
|
|
|
)
|
2019-10-21 05:41:05 +00:00
|
|
|
var cfgCountInFlags = make([]cli.Flag, len(cfgWithCountFlags))
|
|
|
|
copy(cfgCountInFlags, cfgWithCountFlags)
|
2019-12-27 09:11:57 +00:00
|
|
|
cfgCountInFlags = append(cfgCountInFlags,
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "in, i",
|
|
|
|
Usage: "Input file (stdin if not given)",
|
|
|
|
},
|
2020-02-06 15:47:03 +00:00
|
|
|
cli.StringFlag{
|
|
|
|
Name: "dump",
|
|
|
|
Usage: "directory for storing JSON dumps",
|
|
|
|
},
|
2021-07-12 14:59:28 +00:00
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "incremental, n",
|
|
|
|
Usage: "use if dump is incremental",
|
|
|
|
},
|
2019-12-27 09:11:57 +00:00
|
|
|
)
|
2022-10-20 10:59:35 +00:00
|
|
|
var cfgHeightFlags = make([]cli.Flag, len(cfgFlags)+1)
|
|
|
|
copy(cfgHeightFlags, cfgFlags)
|
|
|
|
cfgHeightFlags[len(cfgHeightFlags)-1] = cli.UintFlag{
|
|
|
|
Name: "height",
|
|
|
|
Usage: "Height of the state to reset DB to",
|
|
|
|
Required: true,
|
|
|
|
}
|
2019-10-21 05:41:05 +00:00
|
|
|
return []cli.Command{
|
|
|
|
{
|
2022-08-05 13:23:50 +00:00
|
|
|
Name: "node",
|
2022-12-07 13:51:03 +00:00
|
|
|
Usage: "start a NeoGo node",
|
2023-05-12 13:53:57 +00:00
|
|
|
UsageText: "neo-go node [--config-path path] [-d] [-p/-m/-t] [--config-file file]",
|
2022-08-05 13:23:50 +00:00
|
|
|
Action: startServer,
|
|
|
|
Flags: cfgFlags,
|
2018-02-09 16:08:50 +00:00
|
|
|
},
|
2019-10-21 05:41:05 +00:00
|
|
|
{
|
|
|
|
Name: "db",
|
|
|
|
Usage: "database manipulations",
|
|
|
|
Subcommands: []cli.Command{
|
|
|
|
{
|
2022-08-05 13:23:50 +00:00
|
|
|
Name: "dump",
|
|
|
|
Usage: "dump blocks (starting with block #1) to the file",
|
2023-05-12 13:53:57 +00:00
|
|
|
UsageText: "neo-go db dump -o file [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
|
2022-08-05 13:23:50 +00:00
|
|
|
Action: dumpDB,
|
|
|
|
Flags: cfgCountOutFlags,
|
2019-10-21 05:41:05 +00:00
|
|
|
},
|
|
|
|
{
|
2022-08-05 13:23:50 +00:00
|
|
|
Name: "restore",
|
|
|
|
Usage: "restore blocks from the file",
|
2023-05-12 13:53:57 +00:00
|
|
|
UsageText: "neo-go db restore -i file [--dump] [-n] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
|
2022-08-05 13:23:50 +00:00
|
|
|
Action: restoreDB,
|
|
|
|
Flags: cfgCountInFlags,
|
2019-10-21 05:41:05 +00:00
|
|
|
},
|
2022-10-20 10:59:35 +00:00
|
|
|
{
|
|
|
|
Name: "reset",
|
|
|
|
Usage: "reset database to the previous state",
|
2023-05-12 13:53:57 +00:00
|
|
|
UsageText: "neo-go db reset --height height [--config-path path] [-p/-m/-t] [--config-file file]",
|
2022-10-20 10:59:35 +00:00
|
|
|
Action: resetDB,
|
|
|
|
Flags: cfgHeightFlags,
|
|
|
|
},
|
2019-10-21 05:41:05 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-02-09 16:08:50 +00:00
|
|
|
}
|
|
|
|
|
2019-02-19 11:48:48 +00:00
|
|
|
func newGraceContext() context.Context {
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
stop := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(stop, os.Interrupt)
|
2022-08-24 08:43:21 +00:00
|
|
|
signal.Notify(stop, syscall.SIGTERM)
|
2019-02-19 11:48:48 +00:00
|
|
|
go func() {
|
|
|
|
<-stop
|
|
|
|
cancel()
|
|
|
|
}()
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
2019-12-30 07:43:05 +00:00
|
|
|
func initBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *metrics.Service, *metrics.Service, error) {
|
2022-11-14 07:22:52 +00:00
|
|
|
chain, _, err := initBlockChain(cfg, log)
|
2019-12-16 14:04:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, cli.NewExitError(err, 1)
|
|
|
|
}
|
2020-08-25 12:41:50 +00:00
|
|
|
configureAddresses(&cfg.ApplicationConfiguration)
|
2019-12-30 07:43:05 +00:00
|
|
|
prometheus := metrics.NewPrometheusService(cfg.ApplicationConfiguration.Prometheus, log)
|
|
|
|
pprof := metrics.NewPprofService(cfg.ApplicationConfiguration.Pprof, log)
|
2019-12-16 14:04:18 +00:00
|
|
|
|
|
|
|
go chain.Run()
|
2022-11-25 10:20:53 +00:00
|
|
|
err = prometheus.Start()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, cli.NewExitError(fmt.Errorf("failed to start Prometheus service: %w", err), 1)
|
|
|
|
}
|
|
|
|
err = pprof.Start()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, cli.NewExitError(fmt.Errorf("failed to start Pprof service: %w", err), 1)
|
|
|
|
}
|
2019-12-16 14:04:18 +00:00
|
|
|
|
|
|
|
return chain, prometheus, pprof, nil
|
|
|
|
}
|
|
|
|
|
2019-10-21 05:41:05 +00:00
|
|
|
func dumpDB(ctx *cli.Context) error {
|
2022-08-05 10:32:37 +00:00
|
|
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-10-03 12:05:34 +00:00
|
|
|
cfg, err := options.GetConfigFromContext(ctx)
|
2018-03-15 20:45:37 +00:00
|
|
|
if err != nil {
|
2018-03-17 11:53:21 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
2022-12-05 12:43:55 +00:00
|
|
|
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
|
2019-12-30 07:43:05 +00:00
|
|
|
if err != nil {
|
2019-11-05 12:22:07 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
2022-02-07 15:51:54 +00:00
|
|
|
if logCloser != nil {
|
|
|
|
defer func() { _ = logCloser() }()
|
|
|
|
}
|
2019-12-27 09:11:57 +00:00
|
|
|
count := uint32(ctx.Uint("count"))
|
|
|
|
start := uint32(ctx.Uint("start"))
|
2018-03-09 15:55:25 +00:00
|
|
|
|
2019-10-21 05:41:05 +00:00
|
|
|
var outStream = os.Stdout
|
|
|
|
if out := ctx.String("out"); out != "" {
|
|
|
|
outStream, err = os.Create(out)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer outStream.Close()
|
|
|
|
writer := io.NewBinWriterFromIO(outStream)
|
|
|
|
|
2019-12-30 07:43:05 +00:00
|
|
|
chain, prometheus, pprof, err := initBCWithMetrics(cfg, log)
|
2019-10-21 05:41:05 +00:00
|
|
|
if err != nil {
|
2019-12-16 14:04:18 +00:00
|
|
|
return err
|
2019-10-21 05:41:05 +00:00
|
|
|
}
|
2021-11-17 14:33:37 +00:00
|
|
|
defer func() {
|
|
|
|
pprof.ShutDown()
|
|
|
|
prometheus.ShutDown()
|
|
|
|
chain.Close()
|
|
|
|
}()
|
2019-10-21 05:41:05 +00:00
|
|
|
|
2019-12-27 09:11:57 +00:00
|
|
|
chainCount := chain.BlockHeight() + 1
|
|
|
|
if start+count > chainCount {
|
|
|
|
return cli.NewExitError(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", chainCount-1, count, start), 1)
|
2019-10-21 05:41:05 +00:00
|
|
|
}
|
|
|
|
if count == 0 {
|
2019-12-27 09:11:57 +00:00
|
|
|
count = chainCount - start
|
2019-10-21 05:41:05 +00:00
|
|
|
}
|
2023-06-29 08:41:04 +00:00
|
|
|
if start != 0 {
|
|
|
|
writer.WriteU32LE(start)
|
|
|
|
}
|
2019-12-12 15:52:23 +00:00
|
|
|
writer.WriteU32LE(count)
|
2020-11-24 08:36:09 +00:00
|
|
|
err = chaindump.Dump(chain, writer, start, count)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err.Error(), 1)
|
2019-10-21 05:41:05 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2019-12-16 14:04:18 +00:00
|
|
|
|
2019-10-21 05:41:05 +00:00
|
|
|
func restoreDB(ctx *cli.Context) error {
|
2022-08-05 10:32:37 +00:00
|
|
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-10-03 12:05:34 +00:00
|
|
|
cfg, err := options.GetConfigFromContext(ctx)
|
2018-03-14 09:36:59 +00:00
|
|
|
if err != nil {
|
2019-09-10 14:22:21 +00:00
|
|
|
return err
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
2022-12-05 12:43:55 +00:00
|
|
|
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
|
2019-12-30 07:43:05 +00:00
|
|
|
if err != nil {
|
2019-11-05 12:22:07 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
2022-02-07 15:51:54 +00:00
|
|
|
if logCloser != nil {
|
|
|
|
defer func() { _ = logCloser() }()
|
|
|
|
}
|
2019-12-27 09:11:57 +00:00
|
|
|
count := uint32(ctx.Uint("count"))
|
2018-03-14 09:36:59 +00:00
|
|
|
|
2019-10-21 05:41:05 +00:00
|
|
|
var inStream = os.Stdin
|
|
|
|
if in := ctx.String("in"); in != "" {
|
|
|
|
inStream, err = os.Open(in)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer inStream.Close()
|
|
|
|
reader := io.NewBinReaderFromIO(inStream)
|
|
|
|
|
2020-02-06 15:47:03 +00:00
|
|
|
dumpDir := ctx.String("dump")
|
|
|
|
if dumpDir != "" {
|
2022-12-06 13:34:38 +00:00
|
|
|
cfg.ApplicationConfiguration.SaveStorageBatch = true
|
2020-02-06 15:47:03 +00:00
|
|
|
}
|
|
|
|
|
2019-12-30 07:43:05 +00:00
|
|
|
chain, prometheus, pprof, err := initBCWithMetrics(cfg, log)
|
2019-10-21 05:41:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-02-08 11:02:46 +00:00
|
|
|
defer func() {
|
|
|
|
pprof.ShutDown()
|
|
|
|
prometheus.ShutDown()
|
|
|
|
chain.Close()
|
|
|
|
}()
|
2019-10-21 05:41:05 +00:00
|
|
|
|
2021-07-12 14:59:28 +00:00
|
|
|
var start uint32
|
|
|
|
if ctx.Bool("incremental") {
|
|
|
|
start = reader.ReadU32LE()
|
|
|
|
if chain.BlockHeight()+1 < start {
|
|
|
|
return cli.NewExitError(fmt.Errorf("expected height: %d, dump starts at %d",
|
|
|
|
chain.BlockHeight()+1, start), 1)
|
|
|
|
}
|
2021-07-16 07:45:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var skip uint32
|
|
|
|
if chain.BlockHeight() != 0 {
|
2021-07-12 14:59:28 +00:00
|
|
|
skip = chain.BlockHeight() + 1 - start
|
|
|
|
}
|
|
|
|
|
2019-12-12 15:52:23 +00:00
|
|
|
var allBlocks = reader.ReadU32LE()
|
2019-10-21 05:41:05 +00:00
|
|
|
if reader.Err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
|
|
|
if skip+count > allBlocks {
|
|
|
|
return cli.NewExitError(fmt.Errorf("input file has only %d blocks, can't read %d starting from %d", allBlocks, count, skip), 1)
|
|
|
|
}
|
|
|
|
if count == 0 {
|
2019-12-27 09:15:47 +00:00
|
|
|
count = allBlocks - skip
|
2019-10-21 05:41:05 +00:00
|
|
|
}
|
2021-07-16 07:45:34 +00:00
|
|
|
log.Info("initialize restore",
|
|
|
|
zap.Uint32("start", start),
|
|
|
|
zap.Uint32("height", chain.BlockHeight()),
|
|
|
|
zap.Uint32("skip", skip),
|
|
|
|
zap.Uint32("count", count))
|
2020-02-06 15:47:03 +00:00
|
|
|
|
2020-03-16 09:00:22 +00:00
|
|
|
gctx := newGraceContext()
|
|
|
|
var lastIndex uint32
|
2020-02-06 15:47:03 +00:00
|
|
|
dump := newDump()
|
2020-03-16 09:00:22 +00:00
|
|
|
defer func() {
|
|
|
|
_ = dump.tryPersist(dumpDir, lastIndex)
|
|
|
|
}()
|
2020-02-06 15:47:03 +00:00
|
|
|
|
2020-11-24 08:36:09 +00:00
|
|
|
var f = func(b *block.Block) error {
|
2020-03-16 09:00:22 +00:00
|
|
|
select {
|
|
|
|
case <-gctx.Done():
|
2020-11-24 08:36:09 +00:00
|
|
|
return gctx.Err()
|
2020-03-16 09:00:22 +00:00
|
|
|
default:
|
2020-11-24 08:36:09 +00:00
|
|
|
return nil
|
2020-03-16 09:00:22 +00:00
|
|
|
}
|
2020-11-24 08:36:09 +00:00
|
|
|
}
|
|
|
|
if dumpDir != "" {
|
|
|
|
f = func(b *block.Block) error {
|
|
|
|
select {
|
|
|
|
case <-gctx.Done():
|
|
|
|
return gctx.Err()
|
|
|
|
default:
|
2019-12-27 09:25:39 +00:00
|
|
|
}
|
2020-02-06 15:47:03 +00:00
|
|
|
batch := chain.LastBatch()
|
2020-06-24 13:09:54 +00:00
|
|
|
// The genesis block may already be persisted, so LastBatch() will return nil.
|
2020-11-24 08:36:09 +00:00
|
|
|
if batch == nil && b.Index == 0 {
|
|
|
|
return nil
|
2020-06-24 13:09:54 +00:00
|
|
|
}
|
2020-11-24 08:36:09 +00:00
|
|
|
dump.add(b.Index, batch)
|
|
|
|
lastIndex = b.Index
|
|
|
|
if b.Index%1000 == 0 {
|
|
|
|
if err := dump.tryPersist(dumpDir, b.Index); err != nil {
|
|
|
|
return fmt.Errorf("can't dump storage to file: %w", err)
|
2020-03-16 09:00:22 +00:00
|
|
|
}
|
2020-02-06 15:47:03 +00:00
|
|
|
}
|
2020-11-24 08:36:09 +00:00
|
|
|
return nil
|
2020-02-06 15:47:03 +00:00
|
|
|
}
|
2019-10-21 05:41:05 +00:00
|
|
|
}
|
2020-11-24 08:36:09 +00:00
|
|
|
|
|
|
|
err = chaindump.Restore(chain, reader, skip, count, f)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
2019-10-21 05:41:05 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-10-20 10:59:35 +00:00
|
|
|
func resetDB(ctx *cli.Context) error {
|
|
|
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cfg, err := options.GetConfigFromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
|
|
|
h := uint32(ctx.Uint("height"))
|
|
|
|
|
2022-12-05 12:43:55 +00:00
|
|
|
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
|
2022-10-20 10:59:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
|
|
|
if logCloser != nil {
|
|
|
|
defer func() { _ = logCloser() }()
|
|
|
|
}
|
2022-11-14 07:22:52 +00:00
|
|
|
chain, store, err := initBlockChain(cfg, log)
|
2022-10-20 10:59:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(fmt.Errorf("failed to create Blockchain instance: %w", err), 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = chain.Reset(h)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(fmt.Errorf("failed to reset chain state to height %d: %w", h, err), 1)
|
|
|
|
}
|
2022-11-14 07:22:52 +00:00
|
|
|
err = store.Close()
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(fmt.Errorf("failed to close the DB: %w", err), 1)
|
|
|
|
}
|
2022-10-20 10:59:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-09 12:14:06 +00:00
|
|
|
// oracleService is an interface representing Oracle service with network.Service
|
|
|
|
// capabilities and ability to submit oracle responses.
|
|
|
|
type oracleService interface {
|
|
|
|
rpcsrv.OracleHandler
|
|
|
|
network.Service
|
|
|
|
}
|
|
|
|
|
|
|
|
func mkOracle(config config.OracleConfiguration, magic netmode.Magic, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (oracleService, error) {
|
2022-07-26 18:36:37 +00:00
|
|
|
if !config.Enabled {
|
2022-01-12 02:01:34 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
orcCfg := oracle.Config{
|
|
|
|
Log: log,
|
2022-07-26 18:36:37 +00:00
|
|
|
Network: magic,
|
|
|
|
MainCfg: config,
|
2022-01-12 02:01:34 +00:00
|
|
|
Chain: chain,
|
|
|
|
OnTransaction: serv.RelayTxn,
|
|
|
|
}
|
|
|
|
orc, err := oracle.NewOracle(orcCfg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("can't initialize Oracle module: %w", err)
|
|
|
|
}
|
|
|
|
chain.SetOracle(orc)
|
|
|
|
serv.AddService(orc)
|
|
|
|
return orc, nil
|
|
|
|
}
|
|
|
|
|
2022-12-05 15:11:18 +00:00
|
|
|
func mkConsensus(config config.Consensus, tpb time.Duration, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (consensus.Service, error) {
|
|
|
|
if !config.Enabled {
|
2022-01-12 20:04:07 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
srv, err := consensus.NewService(consensus.Config{
|
|
|
|
Logger: log,
|
|
|
|
Broadcast: serv.BroadcastExtensible,
|
|
|
|
Chain: chain,
|
2023-03-07 09:06:53 +00:00
|
|
|
BlockQueue: serv.GetBlockQueue(),
|
2022-12-06 13:34:38 +00:00
|
|
|
ProtocolConfiguration: chain.GetConfig().ProtocolConfiguration,
|
2022-01-12 20:04:07 +00:00
|
|
|
RequestTx: serv.RequestTx,
|
2022-10-14 18:00:26 +00:00
|
|
|
StopTxFlow: serv.StopTxFlow,
|
2022-12-05 15:11:18 +00:00
|
|
|
Wallet: config.UnlockWallet,
|
2022-07-26 19:41:52 +00:00
|
|
|
TimePerBlock: tpb,
|
2022-01-12 20:04:07 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("can't initialize Consensus module: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-07-28 15:30:14 +00:00
|
|
|
serv.AddConsensusService(srv, srv.OnPayload, srv.OnTransaction)
|
2022-01-12 20:04:07 +00:00
|
|
|
return srv, nil
|
|
|
|
}
|
|
|
|
|
2022-07-26 19:03:58 +00:00
|
|
|
func mkP2PNotary(config config.P2PNotary, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (*notary.Notary, error) {
|
|
|
|
if !config.Enabled {
|
2022-01-12 20:21:09 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if !chain.P2PSigExtensionsEnabled() {
|
|
|
|
return nil, errors.New("P2PSigExtensions are disabled, but Notary service is enabled")
|
|
|
|
}
|
|
|
|
cfg := notary.Config{
|
2022-07-26 19:03:58 +00:00
|
|
|
MainCfg: config,
|
2022-01-12 20:21:09 +00:00
|
|
|
Chain: chain,
|
|
|
|
Log: log,
|
|
|
|
}
|
|
|
|
n, err := notary.NewNotary(cfg, serv.Net, serv.GetNotaryPool(), func(tx *transaction.Transaction) error {
|
2022-04-05 14:19:56 +00:00
|
|
|
err := serv.RelayTxn(tx)
|
2023-07-26 16:18:24 +00:00
|
|
|
if err != nil && !errors.Is(err, core.ErrAlreadyExists) && !errors.Is(err, core.ErrAlreadyInPool) {
|
2022-01-12 20:21:09 +00:00
|
|
|
return fmt.Errorf("can't relay completed notary transaction: hash %s, error: %w", tx.Hash().StringLE(), err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create Notary module: %w", err)
|
|
|
|
}
|
|
|
|
serv.AddService(n)
|
|
|
|
chain.SetNotary(n)
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
2019-10-21 05:41:05 +00:00
|
|
|
func startServer(ctx *cli.Context) error {
|
2022-08-05 10:32:37 +00:00
|
|
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-10-03 12:05:34 +00:00
|
|
|
cfg, err := options.GetConfigFromContext(ctx)
|
2019-10-21 05:41:05 +00:00
|
|
|
if err != nil {
|
2022-01-31 13:20:14 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2019-10-21 05:41:05 +00:00
|
|
|
}
|
2022-12-05 12:43:55 +00:00
|
|
|
var logDebug = ctx.Bool("debug")
|
|
|
|
log, logLevel, logCloser, err := options.HandleLoggingParams(logDebug, cfg.ApplicationConfiguration)
|
2019-12-30 07:43:05 +00:00
|
|
|
if err != nil {
|
2022-01-31 13:20:14 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2019-11-05 12:22:07 +00:00
|
|
|
}
|
2022-02-07 15:51:54 +00:00
|
|
|
if logCloser != nil {
|
|
|
|
defer func() { _ = logCloser() }()
|
|
|
|
}
|
2019-10-21 05:41:05 +00:00
|
|
|
|
|
|
|
grace, cancel := context.WithCancel(newGraceContext())
|
|
|
|
defer cancel()
|
|
|
|
|
2022-11-29 14:43:08 +00:00
|
|
|
serverConfig, err := network.NewServerConfig(cfg)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
2019-10-21 05:41:05 +00:00
|
|
|
|
2019-12-30 07:43:05 +00:00
|
|
|
chain, prometheus, pprof, err := initBCWithMetrics(cfg, log)
|
2019-10-21 05:41:05 +00:00
|
|
|
if err != nil {
|
2022-01-31 13:20:14 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2018-03-17 11:53:21 +00:00
|
|
|
}
|
2022-02-08 11:02:46 +00:00
|
|
|
defer func() {
|
|
|
|
pprof.ShutDown()
|
|
|
|
prometheus.ShutDown()
|
|
|
|
chain.Close()
|
|
|
|
}()
|
2018-03-17 11:53:21 +00:00
|
|
|
|
2022-01-12 21:20:03 +00:00
|
|
|
serv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), log)
|
2020-01-22 08:17:51 +00:00
|
|
|
if err != nil {
|
2020-08-06 16:09:57 +00:00
|
|
|
return cli.NewExitError(fmt.Errorf("failed to create network server: %w", err), 1)
|
2020-01-22 08:17:51 +00:00
|
|
|
}
|
2022-01-13 23:09:16 +00:00
|
|
|
srMod := chain.GetStateModule().(*corestate.Module) // Take full responsibility here.
|
|
|
|
sr, err := stateroot.New(serverConfig.StateRootCfg, srMod, log, chain, serv.BroadcastExtensible)
|
2022-01-12 18:09:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(fmt.Errorf("can't initialize StateRoot service: %w", err), 1)
|
|
|
|
}
|
|
|
|
serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload)
|
|
|
|
|
2022-07-26 18:36:37 +00:00
|
|
|
oracleSrv, err := mkOracle(cfg.ApplicationConfiguration.Oracle, cfg.ProtocolConfiguration.Magic, chain, serv, log)
|
2022-01-12 02:01:34 +00:00
|
|
|
if err != nil {
|
2022-01-31 13:20:14 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2022-01-12 02:01:34 +00:00
|
|
|
}
|
2022-12-05 15:11:18 +00:00
|
|
|
dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.Consensus, serverConfig.TimePerBlock, chain, serv, log)
|
2022-01-12 20:04:07 +00:00
|
|
|
if err != nil {
|
2022-01-31 13:20:14 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2022-01-12 20:04:07 +00:00
|
|
|
}
|
2022-07-26 19:03:58 +00:00
|
|
|
p2pNotary, err := mkP2PNotary(cfg.ApplicationConfiguration.P2PNotary, chain, serv, log)
|
2022-01-12 20:21:09 +00:00
|
|
|
if err != nil {
|
2022-01-31 13:20:14 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2022-01-12 20:21:09 +00:00
|
|
|
}
|
2018-03-23 20:36:59 +00:00
|
|
|
errChan := make(chan error)
|
2022-07-21 13:21:44 +00:00
|
|
|
rpcServer := rpcsrv.New(chain, cfg.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan)
|
2022-04-22 08:47:36 +00:00
|
|
|
serv.AddService(&rpcServer)
|
2018-03-23 20:36:59 +00:00
|
|
|
|
2023-04-13 09:00:52 +00:00
|
|
|
go serv.Start()
|
2022-04-22 08:47:36 +00:00
|
|
|
if !cfg.ApplicationConfiguration.RPC.StartWhenSynchronized {
|
2023-04-13 08:53:58 +00:00
|
|
|
// Run RPC server in a separate routine. This is necessary to avoid a potential
|
|
|
|
// deadlock: Start() can write errors to errChan which is not yet read in the
|
|
|
|
// current execution context (see for-loop below).
|
|
|
|
go rpcServer.Start()
|
2022-04-22 08:47:36 +00:00
|
|
|
}
|
2018-03-23 20:36:59 +00:00
|
|
|
|
2022-07-26 18:36:37 +00:00
|
|
|
sigCh := make(chan os.Signal, 1)
|
2022-07-27 09:51:41 +00:00
|
|
|
signal.Notify(sigCh, sighup)
|
|
|
|
signal.Notify(sigCh, sigusr1)
|
|
|
|
signal.Notify(sigCh, sigusr2)
|
2021-04-30 12:53:27 +00:00
|
|
|
|
2022-01-31 13:20:14 +00:00
|
|
|
fmt.Fprintln(ctx.App.Writer, Logo())
|
2020-08-28 09:11:19 +00:00
|
|
|
fmt.Fprintln(ctx.App.Writer, serv.UserAgent)
|
|
|
|
fmt.Fprintln(ctx.App.Writer)
|
2018-03-25 10:45:54 +00:00
|
|
|
|
|
|
|
var shutdownErr error
|
2018-03-23 20:36:59 +00:00
|
|
|
Main:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case err := <-errChan:
|
2020-08-06 14:44:08 +00:00
|
|
|
shutdownErr = fmt.Errorf("server error: %w", err)
|
2019-02-19 11:48:48 +00:00
|
|
|
cancel()
|
2022-07-26 18:36:37 +00:00
|
|
|
case sig := <-sigCh:
|
2022-12-05 12:43:55 +00:00
|
|
|
var newLogLevel = zapcore.InvalidLevel
|
|
|
|
|
2022-07-26 12:18:30 +00:00
|
|
|
log.Info("signal received", zap.Stringer("name", sig))
|
2022-10-03 12:05:34 +00:00
|
|
|
cfgnew, err := options.GetConfigFromContext(ctx)
|
2022-07-26 12:18:30 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Warn("can't reread the config file, signal ignored", zap.Error(err))
|
|
|
|
break // Continue working.
|
|
|
|
}
|
|
|
|
if !cfg.ProtocolConfiguration.Equals(&cfgnew.ProtocolConfiguration) {
|
|
|
|
log.Warn("ProtocolConfiguration changed, signal ignored")
|
|
|
|
break // Continue working.
|
2022-07-26 13:16:48 +00:00
|
|
|
}
|
|
|
|
if !cfg.ApplicationConfiguration.EqualsButServices(&cfgnew.ApplicationConfiguration) {
|
|
|
|
log.Warn("ApplicationConfiguration changed in incompatible way, signal ignored")
|
|
|
|
break // Continue working.
|
2022-07-26 12:18:30 +00:00
|
|
|
}
|
2022-12-05 12:43:55 +00:00
|
|
|
if !logDebug && cfgnew.ApplicationConfiguration.LogLevel != cfg.ApplicationConfiguration.LogLevel {
|
|
|
|
newLogLevel, err = zapcore.ParseLevel(cfgnew.ApplicationConfiguration.LogLevel)
|
|
|
|
if err != nil {
|
|
|
|
log.Warn("wrong LogLevel in ApplicationConfiguration, signal ignored", zap.Error(err))
|
|
|
|
break // Continue working.
|
|
|
|
}
|
|
|
|
}
|
2022-07-26 13:19:51 +00:00
|
|
|
configureAddresses(&cfgnew.ApplicationConfiguration)
|
2021-04-30 12:53:27 +00:00
|
|
|
switch sig {
|
2022-07-27 09:51:41 +00:00
|
|
|
case sighup:
|
2022-12-05 12:43:55 +00:00
|
|
|
if newLogLevel != zapcore.InvalidLevel {
|
|
|
|
logLevel.SetLevel(newLogLevel)
|
|
|
|
log.Warn("using new logging level", zap.Stringer("level", newLogLevel))
|
|
|
|
}
|
2022-07-27 08:25:58 +00:00
|
|
|
serv.DelService(&rpcServer)
|
2022-04-22 07:49:06 +00:00
|
|
|
rpcServer.Shutdown()
|
2022-07-26 13:19:51 +00:00
|
|
|
rpcServer = rpcsrv.New(chain, cfgnew.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan)
|
2022-07-27 08:25:58 +00:00
|
|
|
serv.AddService(&rpcServer)
|
2022-07-26 13:19:51 +00:00
|
|
|
if !cfgnew.ApplicationConfiguration.RPC.StartWhenSynchronized || serv.IsInSync() {
|
2023-04-13 08:53:58 +00:00
|
|
|
// Here similar to the initial run (see above for-loop), so async.
|
|
|
|
go rpcServer.Start()
|
2022-04-22 08:47:36 +00:00
|
|
|
}
|
2022-07-26 14:19:30 +00:00
|
|
|
pprof.ShutDown()
|
|
|
|
pprof = metrics.NewPprofService(cfgnew.ApplicationConfiguration.Pprof, log)
|
2022-11-25 10:20:53 +00:00
|
|
|
err = pprof.Start()
|
|
|
|
if err != nil {
|
|
|
|
shutdownErr = fmt.Errorf("failed to start Pprof service: %w", err)
|
|
|
|
cancel() // Fatal error, like for RPC server.
|
|
|
|
}
|
2022-07-26 14:19:30 +00:00
|
|
|
prometheus.ShutDown()
|
|
|
|
prometheus = metrics.NewPrometheusService(cfgnew.ApplicationConfiguration.Prometheus, log)
|
2022-11-25 10:20:53 +00:00
|
|
|
err = prometheus.Start()
|
|
|
|
if err != nil {
|
|
|
|
shutdownErr = fmt.Errorf("failed to start Prometheus service: %w", err)
|
|
|
|
cancel() // Fatal error, like for RPC server.
|
|
|
|
}
|
2022-07-27 09:51:41 +00:00
|
|
|
case sigusr1:
|
2022-07-26 18:36:37 +00:00
|
|
|
if oracleSrv != nil {
|
2022-07-27 08:25:58 +00:00
|
|
|
serv.DelService(oracleSrv)
|
2022-07-26 18:36:37 +00:00
|
|
|
chain.SetOracle(nil)
|
|
|
|
rpcServer.SetOracleHandler(nil)
|
|
|
|
oracleSrv.Shutdown()
|
|
|
|
}
|
|
|
|
oracleSrv, err = mkOracle(cfgnew.ApplicationConfiguration.Oracle, cfgnew.ProtocolConfiguration.Magic, chain, serv, log)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("failed to create oracle service", zap.Error(err))
|
|
|
|
break // Keep going.
|
|
|
|
}
|
|
|
|
if oracleSrv != nil {
|
|
|
|
rpcServer.SetOracleHandler(oracleSrv)
|
|
|
|
if serv.IsInSync() {
|
|
|
|
oracleSrv.Start()
|
|
|
|
}
|
|
|
|
}
|
2022-07-26 19:03:58 +00:00
|
|
|
if p2pNotary != nil {
|
2022-07-27 08:25:58 +00:00
|
|
|
serv.DelService(p2pNotary)
|
2022-07-26 19:03:58 +00:00
|
|
|
chain.SetNotary(nil)
|
|
|
|
p2pNotary.Shutdown()
|
|
|
|
}
|
|
|
|
p2pNotary, err = mkP2PNotary(cfgnew.ApplicationConfiguration.P2PNotary, chain, serv, log)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("failed to create notary service", zap.Error(err))
|
|
|
|
break // Keep going.
|
|
|
|
}
|
|
|
|
if p2pNotary != nil && serv.IsInSync() {
|
|
|
|
p2pNotary.Start()
|
|
|
|
}
|
2022-07-27 08:25:58 +00:00
|
|
|
serv.DelExtensibleService(sr, stateroot.Category)
|
2022-07-26 19:27:36 +00:00
|
|
|
srMod.SetUpdateValidatorsCallback(nil)
|
|
|
|
sr.Shutdown()
|
|
|
|
sr, err = stateroot.New(cfgnew.ApplicationConfiguration.StateRoot, srMod, log, chain, serv.BroadcastExtensible)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("failed to create state validation service", zap.Error(err))
|
|
|
|
break // The show must go on.
|
|
|
|
}
|
|
|
|
serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload)
|
|
|
|
if serv.IsInSync() {
|
|
|
|
sr.Start()
|
|
|
|
}
|
2022-07-27 09:51:41 +00:00
|
|
|
case sigusr2:
|
2022-07-26 19:41:52 +00:00
|
|
|
if dbftSrv != nil {
|
2022-07-28 15:30:14 +00:00
|
|
|
serv.DelConsensusService(dbftSrv)
|
2022-07-26 19:41:52 +00:00
|
|
|
dbftSrv.Shutdown()
|
|
|
|
}
|
2022-12-05 15:11:18 +00:00
|
|
|
dbftSrv, err = mkConsensus(cfgnew.ApplicationConfiguration.Consensus, serverConfig.TimePerBlock, chain, serv, log)
|
2022-07-26 19:41:52 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("failed to create consensus service", zap.Error(err))
|
|
|
|
break // Whatever happens, I'll leave it all to chance.
|
|
|
|
}
|
|
|
|
if dbftSrv != nil && serv.IsInSync() {
|
|
|
|
dbftSrv.Start()
|
|
|
|
}
|
2021-04-30 12:53:27 +00:00
|
|
|
}
|
2022-07-26 13:19:51 +00:00
|
|
|
cfg = cfgnew
|
2019-02-19 11:48:48 +00:00
|
|
|
case <-grace.Done():
|
2022-07-26 18:36:37 +00:00
|
|
|
signal.Stop(sigCh)
|
2020-02-17 12:17:02 +00:00
|
|
|
serv.Shutdown()
|
2018-03-23 20:36:59 +00:00
|
|
|
break Main
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if shutdownErr != nil {
|
|
|
|
return cli.NewExitError(shutdownErr, 1)
|
|
|
|
}
|
|
|
|
|
2018-02-09 16:08:50 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-04 06:48:32 +00:00
|
|
|
// configureAddresses sets up addresses for RPC, Prometheus and Pprof depending from the provided config.
|
|
|
|
// In case RPC or Prometheus or Pprof Address provided each of them will use it.
|
|
|
|
// In case global Address (of the node) provided and RPC/Prometheus/Pprof don't have configured addresses they will
|
|
|
|
// use global one. So Node and RPC and Prometheus and Pprof will run on one address.
|
2020-08-25 12:41:50 +00:00
|
|
|
func configureAddresses(cfg *config.ApplicationConfiguration) {
|
2022-11-29 14:43:08 +00:00
|
|
|
if cfg.Address != nil && *cfg.Address != "" { //nolint:staticcheck // SA1019: cfg.Address is deprecated
|
|
|
|
if cfg.RPC.Address == nil || *cfg.RPC.Address == "" { //nolint:staticcheck // SA1019: cfg.RPC.Address is deprecated
|
|
|
|
cfg.RPC.Address = cfg.Address //nolint:staticcheck // SA1019: cfg.RPC.Address is deprecated
|
2019-11-05 12:22:07 +00:00
|
|
|
}
|
2022-11-29 14:43:08 +00:00
|
|
|
if cfg.Prometheus.Address == nil || *cfg.Prometheus.Address == "" { //nolint:staticcheck // SA1019: cfg.Prometheus.Address is deprecated
|
|
|
|
cfg.Prometheus.Address = cfg.Address //nolint:staticcheck // SA1019: cfg.Prometheus.Address is deprecated
|
2019-12-04 06:48:32 +00:00
|
|
|
}
|
2022-11-29 14:43:08 +00:00
|
|
|
if cfg.Pprof.Address == nil || *cfg.Pprof.Address == "" { //nolint:staticcheck // SA1019: cfg.Pprof.Address is deprecated
|
|
|
|
cfg.Pprof.Address = cfg.Address //nolint:staticcheck // SA1019: cfg.Pprof.Address is deprecated
|
2019-11-05 12:22:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-10 14:22:21 +00:00
|
|
|
// initBlockChain initializes BlockChain with preselected DB.
|
2022-11-14 07:22:52 +00:00
|
|
|
func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, storage.Store, error) {
|
2019-09-16 15:52:47 +00:00
|
|
|
store, err := storage.NewStore(cfg.ApplicationConfiguration.DBConfiguration)
|
2019-09-10 14:22:21 +00:00
|
|
|
if err != nil {
|
2022-11-14 07:22:52 +00:00
|
|
|
return nil, nil, cli.NewExitError(fmt.Errorf("could not initialize storage: %w", err), 1)
|
2019-09-10 14:22:21 +00:00
|
|
|
}
|
|
|
|
|
2022-12-06 13:34:38 +00:00
|
|
|
chain, err := core.NewBlockchain(store, cfg.Blockchain(), log)
|
2019-09-10 14:22:21 +00:00
|
|
|
if err != nil {
|
2022-11-14 07:22:52 +00:00
|
|
|
errText := "could not initialize blockchain: %w"
|
2023-04-03 10:34:24 +00:00
|
|
|
errArgs := []any{err}
|
2022-11-14 07:22:52 +00:00
|
|
|
closeErr := store.Close()
|
|
|
|
if closeErr != nil {
|
|
|
|
errText += "; failed to close the DB: %w"
|
|
|
|
errArgs = append(errArgs, closeErr)
|
|
|
|
}
|
|
|
|
|
2022-11-16 08:34:29 +00:00
|
|
|
return nil, nil, cli.NewExitError(fmt.Errorf(errText, errArgs...), 1)
|
2019-09-10 14:22:21 +00:00
|
|
|
}
|
2022-11-14 07:22:52 +00:00
|
|
|
return chain, store, nil
|
2019-09-10 14:22:21 +00:00
|
|
|
}
|
|
|
|
|
2022-12-07 13:51:03 +00:00
|
|
|
// Logo returns NeoGo logo.
|
2022-01-31 13:20:14 +00:00
|
|
|
func Logo() string {
|
2018-03-17 11:53:21 +00:00
|
|
|
return `
|
|
|
|
_ ____________ __________
|
|
|
|
/ | / / ____/ __ \ / ____/ __ \
|
|
|
|
/ |/ / __/ / / / /_____/ / __/ / / /
|
|
|
|
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
|
|
|
|
/_/ |_/_____/\____/ \____/\____/
|
|
|
|
`
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|