github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/snowman/syncer/state_syncer.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package syncer 5 6 import ( 7 "context" 8 "fmt" 9 "math" 10 11 "go.uber.org/zap" 12 13 "github.com/MetalBlockchain/metalgo/database" 14 "github.com/MetalBlockchain/metalgo/ids" 15 "github.com/MetalBlockchain/metalgo/proto/pb/p2p" 16 "github.com/MetalBlockchain/metalgo/snow" 17 "github.com/MetalBlockchain/metalgo/snow/engine/common" 18 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block" 19 "github.com/MetalBlockchain/metalgo/snow/validators" 20 "github.com/MetalBlockchain/metalgo/utils/logging" 21 "github.com/MetalBlockchain/metalgo/utils/set" 22 "github.com/MetalBlockchain/metalgo/version" 23 24 safemath "github.com/MetalBlockchain/metalgo/utils/math" 25 ) 26 27 // maxOutstandingBroadcastRequests is the maximum number of requests to have 28 // outstanding when broadcasting. 29 const maxOutstandingBroadcastRequests = 50 30 31 var _ common.StateSyncer = (*stateSyncer)(nil) 32 33 // summary content as received from network, along with accumulated weight. 34 type weightedSummary struct { 35 summary block.StateSummary 36 weight uint64 37 } 38 39 type stateSyncer struct { 40 Config 41 42 // list of NoOpsHandler for messages dropped by state syncer 43 common.AcceptedFrontierHandler 44 common.AcceptedHandler 45 common.AncestorsHandler 46 common.PutHandler 47 common.QueryHandler 48 common.ChitsHandler 49 common.AppHandler 50 51 started bool 52 53 // Tracks the last requestID that was used in a request 54 requestID uint32 55 56 stateSyncVM block.StateSyncableVM 57 onDoneStateSyncing func(ctx context.Context, lastReqID uint32) error 58 59 // we track the (possibly nil) local summary to help engine 60 // choosing among multiple validated summaries 61 locallyAvailableSummary block.StateSummary 62 63 // Holds the beacons that were sampled for the accepted frontier 64 // Won't be consumed as seeders are reached out. Used to rescale 65 // alpha for frontiers 66 frontierSeeders validators.Manager 67 // IDs of validators we should request state summary frontier from. 68 // Will be consumed seeders are reached out for frontier. 69 targetSeeders set.Set[ids.NodeID] 70 // IDs of validators we requested a state summary frontier from 71 // but haven't received a reply yet. ID is cleared if/when reply arrives. 72 pendingSeeders set.Set[ids.NodeID] 73 // IDs of validators that failed to respond with their state summary frontier 74 failedSeeders set.Set[ids.NodeID] 75 76 // IDs of validators we should request filtering the accepted state summaries from 77 targetVoters set.Set[ids.NodeID] 78 // IDs of validators we requested filtering the accepted state summaries from 79 // but haven't received a reply yet. ID is cleared if/when reply arrives. 80 pendingVoters set.Set[ids.NodeID] 81 // IDs of validators that failed to respond with their filtered accepted state summaries 82 failedVoters set.Set[ids.NodeID] 83 84 // summaryID --> (summary, weight) 85 weightedSummaries map[ids.ID]*weightedSummary 86 87 // summaries received may be different even if referring to the same height 88 // we keep a list of deduplicated height ready for voting 89 summariesHeights set.Set[uint64] 90 uniqueSummariesHeights []uint64 91 } 92 93 func New( 94 cfg Config, 95 onDoneStateSyncing func(ctx context.Context, lastReqID uint32) error, 96 ) common.StateSyncer { 97 ssVM, _ := cfg.VM.(block.StateSyncableVM) 98 return &stateSyncer{ 99 Config: cfg, 100 AcceptedFrontierHandler: common.NewNoOpAcceptedFrontierHandler(cfg.Ctx.Log), 101 AcceptedHandler: common.NewNoOpAcceptedHandler(cfg.Ctx.Log), 102 AncestorsHandler: common.NewNoOpAncestorsHandler(cfg.Ctx.Log), 103 PutHandler: common.NewNoOpPutHandler(cfg.Ctx.Log), 104 QueryHandler: common.NewNoOpQueryHandler(cfg.Ctx.Log), 105 ChitsHandler: common.NewNoOpChitsHandler(cfg.Ctx.Log), 106 AppHandler: cfg.VM, 107 stateSyncVM: ssVM, 108 onDoneStateSyncing: onDoneStateSyncing, 109 } 110 } 111 112 func (ss *stateSyncer) Context() *snow.ConsensusContext { 113 return ss.Ctx 114 } 115 116 func (ss *stateSyncer) Start(ctx context.Context, startReqID uint32) error { 117 ss.Ctx.Log.Info("starting state sync") 118 119 ss.Ctx.State.Set(snow.EngineState{ 120 Type: p2p.EngineType_ENGINE_TYPE_SNOWMAN, 121 State: snow.StateSyncing, 122 }) 123 if err := ss.VM.SetState(ctx, snow.StateSyncing); err != nil { 124 return fmt.Errorf("failed to notify VM that state syncing has started: %w", err) 125 } 126 127 ss.requestID = startReqID 128 129 return ss.tryStartSyncing(ctx) 130 } 131 132 func (ss *stateSyncer) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion *version.Application) error { 133 if err := ss.VM.Connected(ctx, nodeID, nodeVersion); err != nil { 134 return err 135 } 136 137 if err := ss.StartupTracker.Connected(ctx, nodeID, nodeVersion); err != nil { 138 return err 139 } 140 141 return ss.tryStartSyncing(ctx) 142 } 143 144 func (ss *stateSyncer) Disconnected(ctx context.Context, nodeID ids.NodeID) error { 145 if err := ss.VM.Disconnected(ctx, nodeID); err != nil { 146 return err 147 } 148 149 return ss.StartupTracker.Disconnected(ctx, nodeID) 150 } 151 152 // tryStartSyncing will start syncing the first time it is called while the 153 // startupTracker is reporting that the protocol should start. 154 func (ss *stateSyncer) tryStartSyncing(ctx context.Context) error { 155 if ss.started || !ss.StartupTracker.ShouldStart() { 156 return nil 157 } 158 159 ss.started = true 160 return ss.startup(ctx) 161 } 162 163 func (ss *stateSyncer) StateSummaryFrontier(ctx context.Context, nodeID ids.NodeID, requestID uint32, summaryBytes []byte) error { 164 // ignores any late responses 165 if requestID != ss.requestID { 166 ss.Ctx.Log.Debug("received out-of-sync StateSummaryFrontier message", 167 zap.Stringer("nodeID", nodeID), 168 zap.Uint32("expectedRequestID", ss.requestID), 169 zap.Uint32("requestID", requestID), 170 ) 171 return nil 172 } 173 174 if !ss.pendingSeeders.Contains(nodeID) { 175 ss.Ctx.Log.Debug("received unexpected StateSummaryFrontier message", 176 zap.Stringer("nodeID", nodeID), 177 ) 178 return nil 179 } 180 181 // Mark that we received a response from [nodeID] 182 ss.pendingSeeders.Remove(nodeID) 183 184 // retrieve summary ID and register frontier; 185 // make sure next beacons are reached out 186 // even in case invalid summaries are received 187 if summary, err := ss.stateSyncVM.ParseStateSummary(ctx, summaryBytes); err == nil { 188 ss.weightedSummaries[summary.ID()] = &weightedSummary{ 189 summary: summary, 190 } 191 192 height := summary.Height() 193 if !ss.summariesHeights.Contains(height) { 194 ss.summariesHeights.Add(height) 195 ss.uniqueSummariesHeights = append(ss.uniqueSummariesHeights, height) 196 } 197 } else { 198 if ss.Ctx.Log.Enabled(logging.Verbo) { 199 ss.Ctx.Log.Verbo("failed to parse summary", 200 zap.Binary("summary", summaryBytes), 201 zap.Error(err), 202 ) 203 } else { 204 ss.Ctx.Log.Debug("failed to parse summary", 205 zap.Error(err), 206 ) 207 } 208 } 209 210 return ss.receivedStateSummaryFrontier(ctx) 211 } 212 213 func (ss *stateSyncer) GetStateSummaryFrontierFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error { 214 // ignores any late responses 215 if requestID != ss.requestID { 216 ss.Ctx.Log.Debug("received out-of-sync GetStateSummaryFrontierFailed message", 217 zap.Stringer("nodeID", nodeID), 218 zap.Uint32("expectedRequestID", ss.requestID), 219 zap.Uint32("requestID", requestID), 220 ) 221 return nil 222 } 223 224 // Mark that we didn't get a response from [nodeID] 225 ss.failedSeeders.Add(nodeID) 226 ss.pendingSeeders.Remove(nodeID) 227 228 return ss.receivedStateSummaryFrontier(ctx) 229 } 230 231 func (ss *stateSyncer) receivedStateSummaryFrontier(ctx context.Context) error { 232 ss.sendGetStateSummaryFrontiers(ctx) 233 234 // still waiting on requests 235 if ss.pendingSeeders.Len() != 0 { 236 return nil 237 } 238 239 // All nodes reached out for the summary frontier have responded or timed out. 240 // If enough of them have indeed responded we'll go ahead and ask 241 // each state syncer (not just a sample) to filter the list of state summaries 242 // that we were told are on the accepted frontier. 243 // If we got too many timeouts, we restart state syncing hoping that network 244 // problems will go away and we can collect a qualified frontier. 245 // We assume the frontier is qualified after an alpha proportion of frontier seeders have responded 246 frontiersTotalWeight, err := ss.frontierSeeders.TotalWeight(ss.Ctx.SubnetID) 247 if err != nil { 248 return fmt.Errorf("failed to get total weight of frontier seeders for subnet %s: %w", ss.Ctx.SubnetID, err) 249 } 250 beaconsTotalWeight, err := ss.StateSyncBeacons.TotalWeight(ss.Ctx.SubnetID) 251 if err != nil { 252 return fmt.Errorf("failed to get total weight of state sync beacons for subnet %s: %w", ss.Ctx.SubnetID, err) 253 } 254 frontierAlpha := float64(frontiersTotalWeight*ss.Alpha) / float64(beaconsTotalWeight) 255 failedBeaconWeight, err := ss.StateSyncBeacons.SubsetWeight(ss.Ctx.SubnetID, ss.failedSeeders) 256 if err != nil { 257 return fmt.Errorf("failed to get total weight of failed beacons: %w", err) 258 } 259 260 frontierStake := frontiersTotalWeight - failedBeaconWeight 261 if float64(frontierStake) < frontierAlpha { 262 ss.Ctx.Log.Debug("restarting state sync", 263 zap.String("reason", "didn't receive enough frontiers"), 264 zap.Int("numFailedValidators", ss.failedSeeders.Len()), 265 ) 266 return ss.startup(ctx) 267 } 268 269 ss.requestID++ 270 ss.sendGetAcceptedStateSummaries(ctx) 271 return nil 272 } 273 274 func (ss *stateSyncer) AcceptedStateSummary(ctx context.Context, nodeID ids.NodeID, requestID uint32, summaryIDs set.Set[ids.ID]) error { 275 // ignores any late responses 276 if requestID != ss.requestID { 277 ss.Ctx.Log.Debug("received out-of-sync AcceptedStateSummary message", 278 zap.Stringer("nodeID", nodeID), 279 zap.Uint32("expectedRequestID", ss.requestID), 280 zap.Uint32("requestID", requestID), 281 ) 282 return nil 283 } 284 285 if !ss.pendingVoters.Contains(nodeID) { 286 ss.Ctx.Log.Debug("received unexpected AcceptedStateSummary message", 287 zap.Stringer("nodeID", nodeID), 288 ) 289 return nil 290 } 291 292 // Mark that we received a response from [nodeID] 293 ss.pendingVoters.Remove(nodeID) 294 295 nodeWeight := ss.StateSyncBeacons.GetWeight(ss.Ctx.SubnetID, nodeID) 296 ss.Ctx.Log.Debug("adding weight to summaries", 297 zap.Stringer("nodeID", nodeID), 298 zap.Stringer("subnetID", ss.Ctx.SubnetID), 299 zap.Reflect("summaryIDs", summaryIDs), 300 zap.Uint64("nodeWeight", nodeWeight), 301 ) 302 for summaryID := range summaryIDs { 303 ws, ok := ss.weightedSummaries[summaryID] 304 if !ok { 305 ss.Ctx.Log.Debug("skipping summary", 306 zap.String("reason", "unknown summary"), 307 zap.Stringer("nodeID", nodeID), 308 zap.Stringer("summaryID", summaryID), 309 ) 310 continue 311 } 312 313 newWeight, err := safemath.Add64(nodeWeight, ws.weight) 314 if err != nil { 315 ss.Ctx.Log.Error("failed to calculate new summary weight", 316 zap.Stringer("nodeID", nodeID), 317 zap.Stringer("summaryID", summaryID), 318 zap.Uint64("height", ws.summary.Height()), 319 zap.Uint64("nodeWeight", nodeWeight), 320 zap.Uint64("previousWeight", ws.weight), 321 zap.Error(err), 322 ) 323 newWeight = math.MaxUint64 324 } 325 326 ss.Ctx.Log.Verbo("updating summary weight", 327 zap.Stringer("nodeID", nodeID), 328 zap.Stringer("summaryID", summaryID), 329 zap.Uint64("height", ws.summary.Height()), 330 zap.Uint64("previousWeight", ws.weight), 331 zap.Uint64("newWeight", newWeight), 332 ) 333 ws.weight = newWeight 334 } 335 336 ss.sendGetAcceptedStateSummaries(ctx) 337 338 // wait on pending responses 339 if ss.pendingVoters.Len() != 0 { 340 return nil 341 } 342 343 // We've received the filtered accepted frontier from every state sync validator 344 // Drop all summaries without a sufficient weight behind them 345 for summaryID, ws := range ss.weightedSummaries { 346 if ws.weight < ss.Alpha { 347 ss.Ctx.Log.Debug("removing summary", 348 zap.String("reason", "insufficient weight"), 349 zap.Stringer("summaryID", summaryID), 350 zap.Uint64("height", ws.summary.Height()), 351 zap.Uint64("currentWeight", ws.weight), 352 zap.Uint64("requiredWeight", ss.Alpha), 353 ) 354 delete(ss.weightedSummaries, summaryID) 355 } 356 } 357 358 // if we don't have enough weight for the state summary to be accepted then retry or fail the state sync 359 size := len(ss.weightedSummaries) 360 if size == 0 { 361 // retry the state sync if the weight is not enough to state sync 362 failedVotersWeight, err := ss.StateSyncBeacons.SubsetWeight(ss.Ctx.SubnetID, ss.failedVoters) 363 if err != nil { 364 return fmt.Errorf("failed to get total weight of failed voters: %w", err) 365 } 366 367 // if we had too many timeouts when asking for validator votes, we should restart 368 // state sync hoping for the network problems to go away; otherwise, we received 369 // enough (>= ss.Alpha) responses, but no state summary was supported by a majority 370 // of validators (i.e. votes are split between minorities supporting different state 371 // summaries), so there is no point in retrying state sync; we should move ahead to bootstrapping 372 beaconsTotalWeight, err := ss.StateSyncBeacons.TotalWeight(ss.Ctx.SubnetID) 373 if err != nil { 374 return fmt.Errorf("failed to get total weight of state sync beacons for subnet %s: %w", ss.Ctx.SubnetID, err) 375 } 376 votingStakes := beaconsTotalWeight - failedVotersWeight 377 if votingStakes < ss.Alpha { 378 ss.Ctx.Log.Debug("restarting state sync", 379 zap.String("reason", "not enough votes received"), 380 zap.Int("numBeacons", ss.StateSyncBeacons.Count(ss.Ctx.SubnetID)), 381 zap.Int("numFailedSyncers", ss.failedVoters.Len()), 382 ) 383 return ss.startup(ctx) 384 } 385 386 ss.Ctx.Log.Info("skipping state sync", 387 zap.String("reason", "no acceptable summaries found"), 388 ) 389 390 // if we do not restart state sync, move on to bootstrapping. 391 return ss.onDoneStateSyncing(ctx, ss.requestID) 392 } 393 394 preferredStateSummary := ss.selectSyncableStateSummary() 395 syncMode, err := preferredStateSummary.Accept(ctx) 396 if err != nil { 397 return err 398 } 399 400 ss.Ctx.Log.Info("accepted state summary", 401 zap.Stringer("summaryID", preferredStateSummary.ID()), 402 zap.Stringer("syncMode", syncMode), 403 zap.Int("numTotalSummaries", size), 404 ) 405 406 switch syncMode { 407 case block.StateSyncSkipped: 408 // VM did not accept the summary, move on to bootstrapping. 409 return ss.onDoneStateSyncing(ctx, ss.requestID) 410 case block.StateSyncStatic: 411 // Summary was accepted and VM is state syncing. 412 // Engine will wait for notification of state sync done. 413 ss.Ctx.StateSyncing.Set(true) 414 return nil 415 case block.StateSyncDynamic: 416 // Summary was accepted and VM is state syncing. 417 // Engine will continue into bootstrapping and the VM will sync in the 418 // background. 419 ss.Ctx.StateSyncing.Set(true) 420 return ss.onDoneStateSyncing(ctx, ss.requestID) 421 default: 422 ss.Ctx.Log.Warn("unhandled state summary mode, proceeding to bootstrap", 423 zap.Stringer("syncMode", syncMode), 424 ) 425 return ss.onDoneStateSyncing(ctx, ss.requestID) 426 } 427 } 428 429 // selectSyncableStateSummary chooses a state summary from all 430 // the network validated summaries. 431 func (ss *stateSyncer) selectSyncableStateSummary() block.StateSummary { 432 var ( 433 maxSummaryHeight uint64 434 preferredStateSummary block.StateSummary 435 ) 436 437 // by default pick highest summary, unless locallyAvailableSummary is still valid. 438 // In such case we pick locallyAvailableSummary to allow VM resuming state syncing. 439 for id, ws := range ss.weightedSummaries { 440 if ss.locallyAvailableSummary != nil && id == ss.locallyAvailableSummary.ID() { 441 return ss.locallyAvailableSummary 442 } 443 444 height := ws.summary.Height() 445 if maxSummaryHeight <= height { 446 maxSummaryHeight = height 447 preferredStateSummary = ws.summary 448 } 449 } 450 return preferredStateSummary 451 } 452 453 func (ss *stateSyncer) GetAcceptedStateSummaryFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error { 454 // ignores any late responses 455 if requestID != ss.requestID { 456 ss.Ctx.Log.Debug("received out-of-sync GetAcceptedStateSummaryFailed message", 457 zap.Stringer("nodeID", nodeID), 458 zap.Uint32("expectedRequestID", ss.requestID), 459 zap.Uint32("requestID", requestID), 460 ) 461 return nil 462 } 463 464 // If we can't get a response from [nodeID], act as though they said that 465 // they think none of the containers we sent them in GetAccepted are 466 // accepted 467 ss.failedVoters.Add(nodeID) 468 469 return ss.AcceptedStateSummary(ctx, nodeID, requestID, nil) 470 } 471 472 // startup do start the whole state sync process by 473 // sampling frontier seeders, listing state syncers to request votes to 474 // and reaching out frontier seeders if any. Otherwise, it moves immediately 475 // to bootstrapping. Unlike Start, startup does not check 476 // whether sufficient stake amount is connected. 477 func (ss *stateSyncer) startup(ctx context.Context) error { 478 ss.Config.Ctx.Log.Info("starting state sync") 479 480 // clear up messages trackers 481 ss.weightedSummaries = make(map[ids.ID]*weightedSummary) 482 ss.summariesHeights.Clear() 483 ss.uniqueSummariesHeights = nil 484 485 ss.targetSeeders.Clear() 486 ss.pendingSeeders.Clear() 487 ss.failedSeeders.Clear() 488 ss.targetVoters.Clear() 489 ss.pendingVoters.Clear() 490 ss.failedVoters.Clear() 491 492 // sample K beacons to retrieve frontier from 493 beaconIDs, err := ss.StateSyncBeacons.Sample(ss.Ctx.SubnetID, ss.Config.SampleK) 494 if err != nil { 495 return err 496 } 497 498 ss.frontierSeeders = validators.NewManager() 499 for _, nodeID := range beaconIDs { 500 if _, ok := ss.frontierSeeders.GetValidator(ss.Ctx.SubnetID, nodeID); !ok { 501 // Invariant: We never use the TxID or BLS keys populated here. 502 err = ss.frontierSeeders.AddStaker(ss.Ctx.SubnetID, nodeID, nil, ids.Empty, 1) 503 } else { 504 err = ss.frontierSeeders.AddWeight(ss.Ctx.SubnetID, nodeID, 1) 505 } 506 if err != nil { 507 return err 508 } 509 ss.targetSeeders.Add(nodeID) 510 } 511 512 // list all beacons, to reach them for voting on frontier 513 ss.targetVoters.Add(ss.StateSyncBeacons.GetValidatorIDs(ss.Ctx.SubnetID)...) 514 515 // check if there is an ongoing state sync; if so add its state summary 516 // to the frontier to request votes on 517 // Note: database.ErrNotFound means there is no ongoing summary 518 localSummary, err := ss.stateSyncVM.GetOngoingSyncStateSummary(ctx) 519 switch err { 520 case database.ErrNotFound: 521 // no action needed 522 case nil: 523 ss.locallyAvailableSummary = localSummary 524 ss.weightedSummaries[localSummary.ID()] = &weightedSummary{ 525 summary: localSummary, 526 } 527 528 height := localSummary.Height() 529 ss.summariesHeights.Add(height) 530 ss.uniqueSummariesHeights = append(ss.uniqueSummariesHeights, height) 531 default: 532 return err 533 } 534 535 // initiate messages exchange 536 if ss.targetSeeders.Len() == 0 { 537 ss.Ctx.Log.Info("State syncing skipped due to no provided syncers") 538 return ss.onDoneStateSyncing(ctx, ss.requestID) 539 } 540 541 ss.requestID++ 542 ss.sendGetStateSummaryFrontiers(ctx) 543 return nil 544 } 545 546 // Ask up to [common.MaxOutstandingBroadcastRequests] state sync validators at a time 547 // to send their accepted state summary. It is called again until there are 548 // no more seeders to be reached in the pending set 549 func (ss *stateSyncer) sendGetStateSummaryFrontiers(ctx context.Context) { 550 vdrs := set.NewSet[ids.NodeID](1) 551 for ss.targetSeeders.Len() > 0 && ss.pendingSeeders.Len() < maxOutstandingBroadcastRequests { 552 vdr, _ := ss.targetSeeders.Pop() 553 vdrs.Add(vdr) 554 ss.pendingSeeders.Add(vdr) 555 } 556 557 if vdrs.Len() > 0 { 558 ss.Sender.SendGetStateSummaryFrontier(ctx, vdrs, ss.requestID) 559 } 560 } 561 562 // Ask up to [common.MaxOutstandingStateSyncRequests] syncers validators to send 563 // their filtered accepted frontier. It is called again until there are 564 // no more voters to be reached in the pending set. 565 func (ss *stateSyncer) sendGetAcceptedStateSummaries(ctx context.Context) { 566 vdrs := set.NewSet[ids.NodeID](1) 567 for ss.targetVoters.Len() > 0 && ss.pendingVoters.Len() < maxOutstandingBroadcastRequests { 568 vdr, _ := ss.targetVoters.Pop() 569 vdrs.Add(vdr) 570 ss.pendingVoters.Add(vdr) 571 } 572 573 if len(vdrs) > 0 { 574 ss.Sender.SendGetAcceptedStateSummary(ctx, vdrs, ss.requestID, ss.uniqueSummariesHeights) 575 ss.Ctx.Log.Debug("sent GetAcceptedStateSummary messages", 576 zap.Int("numSent", vdrs.Len()), 577 zap.Int("numPending", ss.targetVoters.Len()), 578 ) 579 } 580 } 581 582 func (ss *stateSyncer) Notify(ctx context.Context, msg common.Message) error { 583 if msg != common.StateSyncDone { 584 ss.Ctx.Log.Warn("received an unexpected message from the VM", 585 zap.Stringer("msg", msg), 586 ) 587 return nil 588 } 589 590 ss.Ctx.StateSyncing.Set(false) 591 return ss.onDoneStateSyncing(ctx, ss.requestID) 592 } 593 594 func (*stateSyncer) Gossip(context.Context) error { 595 return nil 596 } 597 598 func (ss *stateSyncer) Shutdown(ctx context.Context) error { 599 ss.Config.Ctx.Log.Info("shutting down state syncer") 600 601 ss.Ctx.Lock.Lock() 602 defer ss.Ctx.Lock.Unlock() 603 604 return ss.VM.Shutdown(ctx) 605 } 606 607 func (*stateSyncer) Halt(context.Context) {} 608 609 func (*stateSyncer) Timeout(context.Context) error { 610 return nil 611 } 612 613 func (ss *stateSyncer) HealthCheck(ctx context.Context) (interface{}, error) { 614 ss.Ctx.Lock.Lock() 615 defer ss.Ctx.Lock.Unlock() 616 617 vmIntf, vmErr := ss.VM.HealthCheck(ctx) 618 intf := map[string]interface{}{ 619 "consensus": struct{}{}, 620 "vm": vmIntf, 621 } 622 return intf, vmErr 623 } 624 625 func (ss *stateSyncer) IsEnabled(ctx context.Context) (bool, error) { 626 if ss.stateSyncVM == nil { 627 // state sync is not implemented 628 return false, nil 629 } 630 631 ss.Ctx.Lock.Lock() 632 defer ss.Ctx.Lock.Unlock() 633 634 return ss.stateSyncVM.StateSyncEnabled(ctx) 635 }