code.vegaprotocol.io/vega@v0.79.0/cmd/data-node/commands/networkhistory/load.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package networkhistory
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/flags"
    27  	"code.vegaprotocol.io/vega/datanode/config"
    28  	"code.vegaprotocol.io/vega/datanode/networkhistory"
    29  	"code.vegaprotocol.io/vega/datanode/networkhistory/snapshot"
    30  	"code.vegaprotocol.io/vega/datanode/networkhistory/store"
    31  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    32  	vgterm "code.vegaprotocol.io/vega/libs/term"
    33  	"code.vegaprotocol.io/vega/logging"
    34  	"code.vegaprotocol.io/vega/paths"
    35  
    36  	"github.com/jackc/pgx/v4/pgxpool"
    37  	"go.uber.org/zap"
    38  )
    39  
    40  type loadCmd struct {
    41  	config.VegaHomeFlag
    42  	config.Config
    43  
    44  	Force             bool   `description:"do not prompt for confirmation"                                   long:"force"              short:"f"`
    45  	WipeExistingData  bool   `description:"Erase all data from the node before loading from network history" long:"wipe-existing-data" short:"w"`
    46  	OptimiseForAppend string `choice:"default"                                                               choice:"true"             choice:"false" default:"default" description:"if true the load will be optimised for appending new segments onto existing datanode data, this is the default if the node already contains data" long:"optimise-for-append" required:"false" short:"a"`
    47  }
    48  
    49  func (cmd *loadCmd) Execute(args []string) error {
    50  	cfg := logging.NewDefaultConfig()
    51  	cfg.Custom.Zap.Level = logging.WarnLevel
    52  	cfg.Environment = "custom"
    53  	log := logging.NewLoggerFromConfig(
    54  		cfg,
    55  	)
    56  	ctx, cancelFn := context.WithCancel(context.Background())
    57  	defer cancelFn()
    58  	defer log.AtExit()
    59  
    60  	vegaPaths := paths.New(cmd.VegaHome)
    61  	err := fixConfig(&cmd.Config, vegaPaths)
    62  	if err != nil {
    63  		return fmt.Errorf("failed to fix config:%w", err)
    64  	}
    65  
    66  	if datanodeLive(cmd.Config) {
    67  		return fmt.Errorf("datanode must be shutdown before data can be loaded")
    68  	}
    69  
    70  	if !cmd.Force && vgterm.HasTTY() {
    71  		if !flags.YesOrNo("Running this command will kill all existing database connections, do you want to continue?") {
    72  			return nil
    73  		}
    74  	}
    75  
    76  	if err := networkhistory.KillAllConnectionsToDatabase(ctx, cmd.SQLStore.ConnectionConfig); err != nil {
    77  		return fmt.Errorf("failed to kill all connections to database: %w", err)
    78  	}
    79  
    80  	connPool, err := getCommandConnPool(ctx, cmd.Config.SQLStore.ConnectionConfig)
    81  	if err != nil {
    82  		return fmt.Errorf("failed to get command connection pool: %w", err)
    83  	}
    84  	defer connPool.Close()
    85  
    86  	hasSchema, err := sqlstore.HasVegaSchema(ctx, connPool)
    87  	if err != nil {
    88  		return fmt.Errorf("failed to check for existing schema:%w", err)
    89  	}
    90  
    91  	if hasSchema {
    92  		err = verifyChainID(ctx, cmd.SQLStore.ConnectionConfig, cmd.ChainID)
    93  		if err != nil {
    94  			if !errors.Is(err, networkhistory.ErrChainNotFound) {
    95  				return fmt.Errorf("failed to verify chain id:%w", err)
    96  			}
    97  		}
    98  	}
    99  
   100  	if hasSchema && cmd.WipeExistingData {
   101  		err := sqlstore.WipeDatabaseAndMigrateSchemaToVersion(log, cmd.Config.SQLStore.ConnectionConfig, 0,
   102  			sqlstore.EmbedMigrations, bool(cmd.Config.SQLStore.VerboseMigration))
   103  		if err != nil {
   104  			return fmt.Errorf("failed to wipe database and migrate schema to version: %w", err)
   105  		}
   106  	}
   107  
   108  	networkHistoryService, err := createNetworkHistoryService(ctx, log, cmd.Config, connPool, vegaPaths)
   109  	if err != nil {
   110  		return fmt.Errorf("failed to created network history service: %w", err)
   111  	}
   112  	defer networkHistoryService.Stop()
   113  
   114  	segments, err := networkHistoryService.ListAllHistorySegments()
   115  	if err != nil {
   116  		return fmt.Errorf("failed to list history segments: %w", err)
   117  	}
   118  
   119  	mostRecentContiguousHistory, err := segments.MostRecentContiguousHistory()
   120  	if err != nil {
   121  		fmt.Println("No history is available to load.  Data can be fetched using the fetch command")
   122  		return nil //nolint:nilerr
   123  	}
   124  
   125  	from := mostRecentContiguousHistory.HeightFrom
   126  	if len(args) >= 1 {
   127  		from, err = strconv.ParseInt(args[0], 10, 64)
   128  		if err != nil {
   129  			return fmt.Errorf("failed to parse from height: %w", err)
   130  		}
   131  	}
   132  
   133  	to := mostRecentContiguousHistory.HeightTo
   134  	if len(args) == 2 {
   135  		to, err = strconv.ParseInt(args[1], 10, 64)
   136  		if err != nil {
   137  			return fmt.Errorf("failed to parse to height: %w", err)
   138  		}
   139  	}
   140  
   141  	contiguousHistory, err := segments.ContiguousHistoryInRange(from, to)
   142  	if err != nil {
   143  		fmt.Printf("No contiguous history is available for block span %d to %d. From and To Heights must match "+
   144  			"from and to heights of the segments in the contiguous history span.\nUse the show command with the '-s' "+
   145  			"option to see all available segments\n", from, to)
   146  		return nil //nolint:nilerr
   147  	}
   148  
   149  	span, err := sqlstore.GetDatanodeBlockSpan(ctx, connPool)
   150  	if err != nil {
   151  		return fmt.Errorf("failed to get datanode block span:%w", err)
   152  	}
   153  
   154  	if from == to || (from >= span.FromHeight && to <= span.ToHeight) {
   155  		fmt.Println("No history is available to load.  Data can be fetched using the fetch command")
   156  		return nil
   157  	}
   158  
   159  	if from < span.FromHeight && to <= span.ToHeight {
   160  		fmt.Printf("Available Network History data spans height %d to %d.  The from height is before the datanodes' block span, %d to %d."+
   161  			" To load history from before the datanodes oldest block you must specify the "+
   162  			" \"--wipe-existing-data\" flag to wipe existing data from the datanode before loading.\n\n", from, to, span.FromHeight, span.ToHeight)
   163  
   164  		return nil
   165  	}
   166  
   167  	if from < span.FromHeight {
   168  		fmt.Printf("Available Network History data spans height %d to %d. However as the datanode already contains"+
   169  			" data from height %d to %d only the history from height %d to %d will be loaded.  To load all the available history data"+
   170  			" run the load command with the \"wipe-existing-data\" flag which will empty the data node before restoring it from the history data\n\n",
   171  			from, to, span.FromHeight, span.ToHeight, span.ToHeight+1, to)
   172  
   173  		if !cmd.Force && vgterm.HasTTY() {
   174  			if !flags.YesOrNo(fmt.Sprintf("Do you wish to continue and load all history from height %d to %d ?", span.ToHeight+1, to)) {
   175  				return nil
   176  			}
   177  		}
   178  	} else {
   179  		fmt.Printf("Network history from block height %d to %d is available to load, current datanode block span is %d to %d\n\n",
   180  			from, to, span.FromHeight, span.ToHeight)
   181  
   182  		if !cmd.Force && vgterm.HasTTY() {
   183  			if !flags.YesOrNo("Do you want to load this history?") {
   184  				return nil
   185  			}
   186  		}
   187  	}
   188  
   189  	optimiseForAppend := false
   190  	switch strings.ToLower(cmd.OptimiseForAppend) {
   191  	case "true":
   192  		optimiseForAppend = true
   193  	case "false":
   194  		optimiseForAppend = false
   195  	default:
   196  		optimiseForAppend = span.HasData
   197  	}
   198  
   199  	if optimiseForAppend {
   200  		fmt.Println("Loading history, optimising for append")
   201  	} else {
   202  		fmt.Println("Loading history, optimising for bulk load")
   203  	}
   204  
   205  	loadLog := newLoadLog()
   206  	defer loadLog.AtExit()
   207  	loaded, err := networkHistoryService.LoadNetworkHistoryIntoDatanodeWithLog(ctx, loadLog, contiguousHistory,
   208  		cmd.Config.SQLStore.ConnectionConfig, optimiseForAppend, bool(cmd.Config.SQLStore.VerboseMigration))
   209  	if err != nil {
   210  		return fmt.Errorf("failed to load all available history:%w", err)
   211  	}
   212  
   213  	fmt.Printf("Loaded history from height %d to %d into the datanode\n", loaded.LoadedFromHeight, loaded.LoadedToHeight)
   214  
   215  	return nil
   216  }
   217  
   218  func createNetworkHistoryService(ctx context.Context, log *logging.Logger, vegaConfig config.Config, connPool *pgxpool.Pool, vegaPaths paths.Paths) (*networkhistory.Service, error) {
   219  	networkHistoryStore, err := store.New(ctx, log, vegaConfig.ChainID, vegaConfig.NetworkHistory.Store, vegaPaths.StatePathFor(paths.DataNodeNetworkHistoryHome),
   220  		vegaConfig.MaxMemoryPercent)
   221  	if err != nil {
   222  		return nil, fmt.Errorf("failed to create network history store: %w", err)
   223  	}
   224  
   225  	snapshotService, err := snapshot.NewSnapshotService(log, vegaConfig.NetworkHistory.Snapshot, connPool, networkHistoryStore,
   226  		vegaPaths.StatePathFor(paths.DataNodeNetworkHistorySnapshotCopyTo), func(version int64) error {
   227  			if err := sqlstore.MigrateUpToSchemaVersion(log, vegaConfig.SQLStore, version, sqlstore.EmbedMigrations); err != nil {
   228  				return fmt.Errorf("failed to migrate to schema version %d: %w", version, err)
   229  			}
   230  			return nil
   231  		},
   232  		func(version int64) error {
   233  			if err := sqlstore.MigrateDownToSchemaVersion(log, vegaConfig.SQLStore, version, sqlstore.EmbedMigrations); err != nil {
   234  				return fmt.Errorf("failed to migrate down to schema version %d: %w", version, err)
   235  			}
   236  			return nil
   237  		})
   238  	if err != nil {
   239  		return nil, fmt.Errorf("failed to create snapshot service: %w", err)
   240  	}
   241  
   242  	networkHistoryService, err := networkhistory.New(ctx, log, vegaConfig.ChainID, vegaConfig.NetworkHistory,
   243  		connPool, snapshotService, networkHistoryStore, vegaConfig.API.Port,
   244  		vegaPaths.StatePathFor(paths.DataNodeNetworkHistorySnapshotCopyTo))
   245  	if err != nil {
   246  		return nil, fmt.Errorf("failed new networkhistory service:%w", err)
   247  	}
   248  	return networkHistoryService, nil
   249  }
   250  
   251  type loadLog struct {
   252  	log *logging.Logger
   253  }
   254  
   255  func newLoadLog() *loadLog {
   256  	cfg := logging.NewDefaultConfig()
   257  	cfg.Custom.Zap.Level = logging.InfoLevel
   258  	cfg.Environment = "custom"
   259  
   260  	return &loadLog{
   261  		log: logging.NewLoggerFromConfig(cfg),
   262  	}
   263  }
   264  
   265  func (l *loadLog) AtExit() {
   266  	l.log.AtExit()
   267  }
   268  
   269  func (l *loadLog) Infof(s string, args ...interface{}) {
   270  	currentTime := time.Now()
   271  	argsWithTime := []any{currentTime.Format("2006-01-02 15:04:05")}
   272  	argsWithTime = append(argsWithTime, args...)
   273  	fmt.Printf("%s "+s+"\n", argsWithTime...)
   274  }
   275  
   276  func (l *loadLog) Info(msg string, fields ...zap.Field) {
   277  	l.log.Info(msg, fields...)
   278  }
   279  
   280  func (l *loadLog) Error(msg string, fields ...zap.Field) {
   281  	l.log.Error(msg, fields...)
   282  }