Skip to content

services: add StateFetcher service for state snapshot downloading from NeoFS #3844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

AliceInHunterland
Copy link
Contributor

@AliceInHunterland AliceInHunterland commented Mar 20, 2025

Close #3793

  • fix tests import cycle
  • documentation

@AliceInHunterland AliceInHunterland force-pushed the statefetcher branch 2 times, most recently from b000eb3 to e39c22a Compare March 21, 2025 06:57

// GetMPTNodes search for the state object and returns MPT nodes.
func (bfs *Service) GetMPTNodes() ([][]byte, error) {
headerHeight := bfs.heightFunc() - 1
Copy link
Contributor Author

@AliceInHunterland AliceInHunterland Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return if is shutdown

@@ -902,6 +906,13 @@ func (s *Server) requestBlocksOrHeaders(p Peer) error {
return fmt.Errorf("%w: %w", errBlocksRequestFailed, err)
}
if requestMPTNodes {
if s.config.NeoFSStateSyncExtensions {
nodes, err := s.syncHeaderFetcher.GetMPTNodes()
Copy link
Contributor Author

@AliceInHunterland AliceInHunterland Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not syncHeaderFetcher, but syncblockFetcher

Comment on lines 232 to 251
startIndex := bfs.heightFunc()/bfs.cfg.IndexFileSize + 1
for {
prm := client.PrmObjectSearch{}
filters := object.NewSearchFilters()
filters.AddFilter(bfs.cfg.IndexFileAttribute, fmt.Sprintf("%d", startIndex), object.MatchStringEqual)
filters.AddFilter("IndexSize", fmt.Sprintf("%d", bfs.cfg.IndexFileSize), object.MatchStringEqual)
prm.SetFilters(filters)

ctx, cancel := context.WithTimeout(bfs.ctx, bfs.cfg.Timeout)
blockOidsObject, err := bfs.objectSearch(ctx, prm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After PR finalisation it will be useful to add a comment about this code to #3744, because it will be affected by index files support removal.

@AliceInHunterland AliceInHunterland changed the title blockfetcher: add GetMPTNodes services: add StateFetcher service for state snapshot downloading from NeoFS Mar 27, 2025
@AliceInHunterland AliceInHunterland force-pushed the statefetcher branch 4 times, most recently from f93aa3c to 2c617ce Compare March 28, 2025 13:10
@AnnaShaleva
Copy link
Member

AnnaShaleva commented Mar 31, 2025

@AliceInHunterland, what's the state of the current PR? Can I review?

@AliceInHunterland
Copy link
Contributor Author

AliceInHunterland commented Mar 31, 2025

Tests not fixed and not tested with blocks with StateRootEnabled. Turning back to p2p sync after trying neofs sync is not yet implemented.

var err error
ctx, cancel := context.WithTimeout(bfs.ctx, bfs.cfg.Timeout)
defer cancel()
for i := headerHeight; true; i-- {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have already discussed that, we need to search for the nearest state sync point, not every block for sure.

return rc, err
}

func (bfs *Service) retry(action func() error) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also the common code. This code should be placed to the same place where common fetchers' settings are handled, and then reused.

Comment on lines 394 to 398
bfs.stateModule.Store.Seek(storage.SeekRange{Prefix: tempPrefix}, func(k, v []byte) bool {
newKey := append(permPrefix, k[1:]...)
cache.Put(newKey, v)
cache.Delete(k)
return true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See how it's handled by StateSync module: firstly remove all items stored by the old key, then replace the active key from the old one to the new one, that's it.

return true
})

if n, err := cache.PersistSync(); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a single persist per the whole batch for sure. It should be batched persist. Have you seen the same pattern somewhere else?

The general idea is that this code must be robust to node shutdown.

Copy link
Member

@AnnaShaleva AnnaShaleva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests/linter are failing. We also need an extension of the default node's configuration.

@AliceInHunterland AliceInHunterland force-pushed the statefetcher branch 3 times, most recently from 9c45630 to ef0500d Compare April 14, 2025 07:59
@AliceInHunterland AliceInHunterland force-pushed the statefetcher branch 2 times, most recently from 719d675 to 9b1616f Compare April 15, 2025 07:36
@AliceInHunterland AliceInHunterland marked this pull request as ready for review April 15, 2025 08:09
Copy link
Member

@AnnaShaleva AnnaShaleva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main point is: since we decide to move storage items based state synchronisation to StateSync Module, it should be completely Module's duty to properly update storage/MPT, store intermediate synchronisation state and properly recover from this state after the node restart. StateFetcher should only manage storage items downloading from the NeoFS.

So all code is written, but it should be properly organized and move to proper places, to keep StateSync module abstracted from storage items source. See the relevant comments in statesync module and statefetcher before the refactoring.

Comment on lines 153 to 157
if err := a.NeoFSStateFetcher.NeoFSService.Validate(); err != nil {
a.NeoFSStateFetcher.NeoFSService = a.NeoFSBlockFetcher.NeoFSService
}
if err := a.NeoFSStateFetcher.Validate(); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly check if NeoFSStateFetcher contains empty container parameters and fill in from NeoFSBlockFetcher section prior to NeoFSStateFetcher validation.

@@ -0,0 +1,95 @@
package config
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/pkg/config/neofsstorage_config.go/pkg/config/neofsservice_config.go

cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)

// NeoFSService represents the configuration for services connected with NeoFS.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

services connected with NeoFS.

services interacting with NeoFS block/state storage


// Equals allows to compare two NeoFSService instances, returns true if
// they're equal.
func (cfg *NeoFSService) Equals(o NeoFSService) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should be used to compare two application config instances. Right now it's unused.

}

// NeoFSStateFetcher represents the configuration for the NeoFS StateFetcher service.
// if NeoFSService configuration is omitted the NeoFSBlockFetcher.NeoFSService will be used.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if NeoFSService configuration is omitted the NeoFSBlockFetcher.NeoFSService will be used.

This comment is not related to the structure documentation. I'd move it either to ApplicationConfiguration.Validate() or to ApplicationConfiguration.NeoFSStateFetcher.

@@ -394,6 +406,7 @@ func (s *Server) Shutdown() {
func (s *Server) stateSyncCallBack() {
needHeaders := s.stateSync.NeedHeaders()
needBlocks := s.stateSync.NeedBlocks()
needMPTs := s.stateSync.NeedMPTNodes()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And given the fact that starting from this commit, statesync Module is able to use both MPT nodes and raw storage items, I'd rename NeedMPTNodes to some NeedStorageData or NeedContractStorageData in a separate commit.

for p := range s.peers {
if p.Handshaked() {
heights = append(heights, p.LastBlockIndex())
if !s.ServerConfig.NeoFSBlockFetcherCfg.Enabled || s.syncStateFetcher.IsShutdown() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part needs some polishing, but let's firstly deal with final statesync Module implementation and after that I'll review network part one more time.

Comment on lines 231 to 233
if err := sfs.saveCheckpointState(tempState); err != nil {
sfs.log.Error("failed to save state checkpoint", zap.Error(err))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part should be managed by statesync module (see the related reviewcomments in statesync module file). StateFetcher should only manage storage items downloading and should then pass these raw storage items to statesync module (exactly like P2P layer passes raw MPT data to statesync Module).

Comment on lines 217 to 224
if err := sfs.chain.AddMPTBatch(batch, syncHeight); err != nil {
sfs.log.Error("failed to process final batch during shutdown", zap.Error(err))
}
if _, err := localMPT.PutBatch(mpt.MapToMPTBatch(batch)); err != nil {
sfs.log.Error("failed to update local MPT during shutdown", zap.Error(err))
}
localMPT.Flush(syncHeight)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this part should also be managed by StateSync Module. I'm sure I saw the similar code in the module, so there shouldn't be duplicating functionality between StateFetcher and Module.

@AliceInHunterland AliceInHunterland force-pushed the statefetcher branch 2 times, most recently from 3d603a2 to 7beed76 Compare April 16, 2025 13:42
Copy link

codecov bot commented Apr 16, 2025

Codecov Report

Attention: Patch coverage is 17.48148% with 557 lines in your changes missing coverage. Please review.

Project coverage is 81.66%. Comparing base (2bdc778) to head (13f91b2).
Report is 28 commits behind head on master.

Files with missing lines Patch % Lines
pkg/services/statefetcher/statefetcher.go 3.30% 234 Missing ⚠️
pkg/core/statesync/module.go 20.00% 198 Missing and 6 partials ⚠️
pkg/services/helpers/neofs/blockstorage.go 0.00% 62 Missing ⚠️
pkg/network/server.go 50.00% 27 Missing and 3 partials ⚠️
cli/util/upload_state.go 0.00% 18 Missing ⚠️
pkg/config/application_config.go 0.00% 4 Missing and 2 partials ⚠️
pkg/config/neofsservice_config.go 89.65% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3844      +/-   ##
==========================================
- Coverage   82.52%   81.66%   -0.86%     
==========================================
  Files         342      343       +1     
  Lines       47887    48598     +711     
==========================================
+ Hits        39518    39687     +169     
- Misses       6734     7266     +532     
- Partials     1635     1645      +10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Sync state via NeoFS block storage
3 participants