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 )