code.vegaprotocol.io/vega@v0.79.0/datanode/networkhistory/initialise.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  	"math/rand"
    23  	"net"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"code.vegaprotocol.io/vega/datanode/entities"
    29  	"code.vegaprotocol.io/vega/datanode/networkhistory/segment"
    30  	"code.vegaprotocol.io/vega/datanode/networkhistory/snapshot"
    31  	"code.vegaprotocol.io/vega/datanode/service"
    32  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    33  	"code.vegaprotocol.io/vega/logging"
    34  	v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2"
    35  
    36  	"google.golang.org/grpc"
    37  )
    38  
    39  var ErrChainNotFound = errors.New("no chain found")
    40  
    41  // it would be nice to use go:generate go run github.com/golang/mock/mockgen -destination mocks/networkhistory_service_mock.go -package mocks code.vegaprotocol.io/vega/datanode/networkhistory NetworkHistory
    42  // but it messes up with generic interfaces and so requires a bit of manual fiddling.
    43  type NetworkHistory interface {
    44  	FetchHistorySegment(ctx context.Context, historySegmentID string) (segment.Full, error)
    45  	LoadNetworkHistoryIntoDatanode(ctx context.Context, chunk segment.ContiguousHistory[segment.Full], cfg sqlstore.ConnectionConfig, withIndexesAndOrderTriggers, verbose bool) (snapshot.LoadResult, error)
    46  	GetMostRecentHistorySegmentFromBootstrapPeers(ctx context.Context, grpcAPIPorts []int) (*PeerResponse, map[string]*v2.GetMostRecentNetworkHistorySegmentResponse, error)
    47  	GetDatanodeBlockSpan(ctx context.Context) (sqlstore.DatanodeBlockSpan, error)
    48  	ListAllHistorySegments() (segment.Segments[segment.Full], error)
    49  }
    50  
    51  func InitialiseDatanodeFromNetworkHistory(ctx context.Context, cfg InitializationConfig, log *logging.Logger,
    52  	connCfg sqlstore.ConnectionConfig, networkHistoryService NetworkHistory,
    53  	grpcPorts []int, verboseMigration bool,
    54  ) error {
    55  	ctxWithTimeout, cancel := context.WithTimeout(ctx, cfg.TimeOut.Duration)
    56  	defer cancel()
    57  
    58  	if len(cfg.ToSegment) == 0 {
    59  		for {
    60  			mostRecentHistorySegment, err := getMostRecentNetworkHistorySegment(ctxWithTimeout, networkHistoryService, grpcPorts, log)
    61  			if err != nil {
    62  				return fmt.Errorf("failed to get most recent history segment: %w", err)
    63  			}
    64  
    65  			toSegmentID := mostRecentHistorySegment.HistorySegmentId
    66  
    67  			currentSpan, err := networkHistoryService.GetDatanodeBlockSpan(ctxWithTimeout)
    68  			if err != nil {
    69  				return fmt.Errorf("failed to get datanode block span: %w", err)
    70  			}
    71  
    72  			var blocksToFetch int64
    73  			if currentSpan.HasData {
    74  				if currentSpan.ToHeight >= mostRecentHistorySegment.ToHeight {
    75  					log.Infof("data node height %d is already at or beyond the height of the most recent history segment %d, no further history to load",
    76  						currentSpan.ToHeight, mostRecentHistorySegment.ToHeight)
    77  					return nil
    78  				}
    79  
    80  				blocksToFetch = mostRecentHistorySegment.ToHeight - currentSpan.ToHeight
    81  			} else {
    82  				// check if goes < 0
    83  				blocksToFetch = cfg.MinimumBlockCount
    84  				if mostRecentHistorySegment.ToHeight-cfg.MinimumBlockCount < 0 {
    85  					blocksToFetch = -1
    86  				}
    87  			}
    88  
    89  			err = loadSegments(ctxWithTimeout, log, connCfg, networkHistoryService, currentSpan,
    90  				toSegmentID, blocksToFetch, verboseMigration)
    91  			if err != nil {
    92  				return fmt.Errorf("failed to load segments: %w", err)
    93  			}
    94  		}
    95  	} else {
    96  		currentSpan, err := networkHistoryService.GetDatanodeBlockSpan(ctx)
    97  		if err != nil {
    98  			return fmt.Errorf("failed to get datanode block span: %w", err)
    99  		}
   100  
   101  		err = loadSegments(ctxWithTimeout, log, connCfg, networkHistoryService, currentSpan,
   102  			cfg.ToSegment, cfg.MinimumBlockCount, verboseMigration)
   103  		if err != nil {
   104  			return fmt.Errorf("failed to load segments: %w", err)
   105  		}
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  func loadSegments(ctx context.Context, log *logging.Logger,
   112  	connCfg sqlstore.ConnectionConfig, networkHistoryService NetworkHistory, currentSpan sqlstore.DatanodeBlockSpan, toSegmentID string, blocksToFetch int64,
   113  	verboseMigration bool,
   114  ) error {
   115  	log.Infof("fetching history using as the first segment:{%s} and minimum blocks to fetch %d", toSegmentID, blocksToFetch)
   116  
   117  	blocksFetched, err := FetchHistoryBlocks(ctx, log.Infof, toSegmentID,
   118  		func(ctx context.Context, historySegmentID string) (FetchResult, error) {
   119  			segment, err := networkHistoryService.FetchHistorySegment(ctx, historySegmentID)
   120  			if err != nil {
   121  				return FetchResult{}, err
   122  			}
   123  			return FromSegmentIndexEntry(segment), nil
   124  		}, blocksToFetch)
   125  	if err != nil {
   126  		log.Errorf("failed to fetch history blocks: %v", err)
   127  	}
   128  
   129  	if blocksFetched == 0 {
   130  		return fmt.Errorf("failed to get any blocks from network history")
   131  	}
   132  
   133  	log.Infof("fetched %d blocks from network history", blocksFetched)
   134  
   135  	log.Infof("loading history into the datanode")
   136  	segments, err := networkHistoryService.ListAllHistorySegments()
   137  	if err != nil {
   138  		return fmt.Errorf("failed to list all history segments: %w", err)
   139  	}
   140  
   141  	chunks := segments.AllContigousHistories()
   142  	if len(chunks) == 0 {
   143  		log.Infof("no network history available to load")
   144  		return nil
   145  	}
   146  
   147  	lastChunk, err := segments.MostRecentContiguousHistory()
   148  	if err != nil {
   149  		return fmt.Errorf("failed to get most recent chunk")
   150  	}
   151  
   152  	if currentSpan.ToHeight >= lastChunk.HeightTo {
   153  		log.Infof("datanode already contains the latest network history data")
   154  		return nil
   155  	}
   156  
   157  	to := lastChunk.HeightTo
   158  	from := lastChunk.HeightFrom
   159  	if currentSpan.HasData {
   160  		for _, segment := range lastChunk.Segments {
   161  			if segment.GetFromHeight() <= (currentSpan.ToHeight+1) && segment.GetToHeight() > currentSpan.ToHeight {
   162  				from = segment.GetFromHeight()
   163  				break
   164  			}
   165  		}
   166  	}
   167  
   168  	chunkToLoad, err := segments.ContiguousHistoryInRange(from, to)
   169  	if err != nil {
   170  		return fmt.Errorf("failed to load history into the datanode: %w", err)
   171  	}
   172  
   173  	loaded, err := networkHistoryService.LoadNetworkHistoryIntoDatanode(ctx, chunkToLoad, connCfg, currentSpan.HasData, verboseMigration)
   174  	if err != nil {
   175  		return fmt.Errorf("failed to load history into the datanode: %w", err)
   176  	}
   177  	log.Infof("loaded history from height %d to %d into the datanode", loaded.LoadedFromHeight, loaded.LoadedToHeight)
   178  
   179  	return nil
   180  }
   181  
   182  func getMostRecentNetworkHistorySegment(ctx context.Context, networkHistoryService NetworkHistory, grpcPorts []int, log *logging.Logger) (*v2.HistorySegment, error) {
   183  	response, _, err := networkHistoryService.GetMostRecentHistorySegmentFromBootstrapPeers(ctx,
   184  		grpcPorts)
   185  	if err != nil {
   186  		log.Errorf("failed to get most recent history segment from peers: %v", err)
   187  		return nil, fmt.Errorf("failed to get most recent history segment from peers: %w", err)
   188  	}
   189  
   190  	if response == nil {
   191  		log.Error("unable to get a most recent segment response from peers")
   192  		return nil, errors.New("unable to get a most recent segment response from peers")
   193  	}
   194  
   195  	mostRecentHistorySegment := response.Response.Segment
   196  
   197  	log.Info("got most recent history segment",
   198  		logging.String("segment", mostRecentHistorySegment.String()), logging.String("peer", response.PeerAddr))
   199  	return mostRecentHistorySegment, nil
   200  }
   201  
   202  func VerifyChainID(chainID string, chainService *service.Chain) error {
   203  	if len(chainID) == 0 {
   204  		return errors.New("chain id must be set")
   205  	}
   206  
   207  	currentChainID, err := chainService.GetChainID()
   208  	if err != nil {
   209  		if errors.Is(err, entities.ErrNotFound) {
   210  			return ErrChainNotFound
   211  		}
   212  
   213  		return fmt.Errorf("failed to get chain id:%w", err)
   214  	}
   215  
   216  	if len(currentChainID) == 0 {
   217  		if err = chainService.SetChainID(chainID); err != nil {
   218  			return fmt.Errorf("failed to set chain id:%w", err)
   219  		}
   220  	} else if currentChainID != chainID {
   221  		return fmt.Errorf("mismatched chain ids, config chain id: %s, current chain id: %s", chainID, currentChainID)
   222  	}
   223  	return nil
   224  }
   225  
   226  type FetchResult struct {
   227  	HeightFrom               int64
   228  	HeightTo                 int64
   229  	PreviousHistorySegmentID string
   230  }
   231  
   232  func FromSegmentIndexEntry(s segment.Full) FetchResult {
   233  	return FetchResult{
   234  		HeightFrom:               s.GetFromHeight(),
   235  		HeightTo:                 s.GetToHeight(),
   236  		PreviousHistorySegmentID: s.GetPreviousHistorySegmentId(),
   237  	}
   238  }
   239  
   240  // FetchHistoryBlocks will keep fetching history until numBlocksToFetch is reached or all history is retrieved.
   241  func FetchHistoryBlocks(ctx context.Context, logInfo func(s string, args ...interface{}), historySegmentID string,
   242  	fetchHistory func(ctx context.Context, historySegmentID string) (FetchResult, error),
   243  	numBlocksToFetch int64,
   244  ) (int64, error) {
   245  	blocksFetched := int64(0)
   246  	for blocksFetched < numBlocksToFetch || numBlocksToFetch == -1 {
   247  		logInfo("fetching history for segment id:%s", historySegmentID)
   248  		indexEntry, err := fetchHistory(ctx, historySegmentID)
   249  		if err != nil {
   250  			return 0, fmt.Errorf("failed to fetch history:%w", err)
   251  		}
   252  		blocksFetched += indexEntry.HeightTo - indexEntry.HeightFrom + 1
   253  
   254  		logInfo("fetched history:%+v", indexEntry)
   255  
   256  		if len(indexEntry.PreviousHistorySegmentID) == 0 {
   257  			break
   258  		}
   259  
   260  		historySegmentID = indexEntry.PreviousHistorySegmentID
   261  		if len(historySegmentID) == 0 {
   262  			break
   263  		}
   264  	}
   265  
   266  	return blocksFetched, nil
   267  }
   268  
   269  type PeerResponse struct {
   270  	PeerAddr string
   271  	Response *v2.GetMostRecentNetworkHistorySegmentResponse
   272  }
   273  
   274  func GetMostRecentHistorySegmentFromPeersAddresses(ctx context.Context, peerAddresses []string,
   275  	swarmKeySeed string,
   276  	grpcAPIPorts []int,
   277  ) (*PeerResponse, map[string]*v2.GetMostRecentNetworkHistorySegmentResponse, error) {
   278  	const maxPeersToContact = 10
   279  
   280  	if len(peerAddresses) > maxPeersToContact {
   281  		peerAddresses = peerAddresses[:maxPeersToContact]
   282  	}
   283  
   284  	ctxWithTimeOut, ctxCancelFn := context.WithTimeout(ctx, 30*time.Second)
   285  	defer ctxCancelFn()
   286  	peerToResponse := map[string]*v2.GetMostRecentNetworkHistorySegmentResponse{}
   287  	var errorMsgs []string
   288  	for _, peerAddress := range peerAddresses {
   289  		for _, grpcAPIPort := range grpcAPIPorts {
   290  			resp, err := GetMostRecentHistorySegmentFromPeer(ctxWithTimeOut, peerAddress, grpcAPIPort)
   291  			if err == nil {
   292  				peerAddress = net.JoinHostPort(peerAddress, strconv.Itoa(grpcAPIPort))
   293  				peerToResponse[peerAddress] = resp
   294  			} else {
   295  				errorMsgs = append(errorMsgs, err.Error())
   296  			}
   297  		}
   298  	}
   299  
   300  	if len(peerToResponse) == 0 {
   301  		return nil, nil, fmt.Errorf(strings.Join(errorMsgs, ","))
   302  	}
   303  
   304  	return SelectMostRecentHistorySegmentResponse(peerToResponse, swarmKeySeed), peerToResponse, nil
   305  }
   306  
   307  func GetMostRecentHistorySegmentFromPeer(ctx context.Context, ip string, datanodeGrpcAPIPort int) (*v2.GetMostRecentNetworkHistorySegmentResponse, error) {
   308  	client, conn, err := GetDatanodeClientFromIPAndPort(ip, datanodeGrpcAPIPort, 20*1024*1024) // @TODO hard-coded 20MB value, same as default config, pass this in correctly
   309  	if err != nil {
   310  		return nil, fmt.Errorf("failed to get datanode client:%w", err)
   311  	}
   312  	defer func() { _ = conn.Close() }()
   313  
   314  	resp, err := client.GetMostRecentNetworkHistorySegment(ctx, &v2.GetMostRecentNetworkHistorySegmentRequest{})
   315  	if err != nil {
   316  		return nil, fmt.Errorf("failed to get most recent history segment:%w", err)
   317  	}
   318  
   319  	return resp, nil
   320  }
   321  
   322  // TODO this needs some thought as to the best strategy to select the response to avoid spoofing.
   323  func SelectMostRecentHistorySegmentResponse(peerToResponse map[string]*v2.GetMostRecentNetworkHistorySegmentResponse, swarmKeySeed string) *PeerResponse {
   324  	responses := make([]PeerResponse, 0, len(peerToResponse))
   325  
   326  	highestResponseHeight := int64(0)
   327  	for peer, response := range peerToResponse {
   328  		if response.SwarmKeySeed == swarmKeySeed {
   329  			responses = append(responses, PeerResponse{peer, response})
   330  
   331  			if response.Segment.ToHeight > highestResponseHeight {
   332  				highestResponseHeight = response.Segment.ToHeight
   333  			}
   334  		}
   335  	}
   336  
   337  	var responsesAtHighestHeight []PeerResponse
   338  	for _, response := range responses {
   339  		if response.Response.Segment.ToHeight == highestResponseHeight {
   340  			responsesAtHighestHeight = append(responsesAtHighestHeight, response)
   341  		}
   342  	}
   343  
   344  	// Select one response from the list at random
   345  	if len(responsesAtHighestHeight) > 0 {
   346  		segment := responsesAtHighestHeight[rand.Intn(len(responsesAtHighestHeight))]
   347  		return &segment
   348  	}
   349  
   350  	return nil
   351  }
   352  
   353  func GetDatanodeClientFromIPAndPort(ip string, port, maxMsgSize int) (v2.TradingDataServiceClient, *grpc.ClientConn, error) {
   354  	address := net.JoinHostPort(ip, strconv.Itoa(port))
   355  	tdconn, err := grpc.Dial(
   356  		address,
   357  		grpc.WithInsecure(),
   358  		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)),
   359  	)
   360  	if err != nil {
   361  		return nil, nil, err
   362  	}
   363  	tradingDataClientV2 := v2.NewTradingDataServiceClient(&clientConn{tdconn})
   364  
   365  	return tradingDataClientV2, tdconn, nil
   366  }
   367  
   368  type (
   369  	clientConn struct {
   370  		*grpc.ClientConn
   371  	}
   372  )