func (s *StatefulSyncer) NetworkStatus(
	ctx context.Context,
	network *types.NetworkIdentifier,
) (*types.NetworkStatusResponse, error) {
	networkStatus, fetchErr := s.fetcher.NetworkStatusRetry(ctx, network, nil)
	if fetchErr != nil {
		return nil, fetchErr.Err
	}
	return networkStatus, nil
}
NetworkStatus is called by the syncer to get the current
network status.
package statefulsyncer
import (
	"context"
	"errors"
	"fmt"
	"log"
	"runtime"
	"time"
	"golang.org/x/sync/semaphore"
	"github.com/coinbase/rosetta-sdk-go/fetcher"
	storageErrs "github.com/coinbase/rosetta-sdk-go/storage/errors"
	"github.com/coinbase/rosetta-sdk-go/storage/modules"
	"github.com/coinbase/rosetta-sdk-go/syncer"
	"github.com/coinbase/rosetta-sdk-go/types"
	"github.com/coinbase/rosetta-sdk-go/utils"
)
var _ syncer.Handler = (*StatefulSyncer)(nil)
var _ syncer.Helper = (*StatefulSyncer)(nil)
const (
	DefaultPruneSleepTime = 10 * time.Minute
	pruneBuffer = 2
	semaphoreWeight = int64(1)
)
type StatefulSyncer struct {
	network        *types.NetworkIdentifier
	fetcher        *fetcher.Fetcher
	cancel         context.CancelFunc
	blockStorage   *modules.BlockStorage
	counterStorage *modules.CounterStorage
	logger         Logger
	workers        []modules.BlockWorker
	cacheSize        int
	maxConcurrency   int64
	pastBlockLimit   int
	adjustmentWindow int64
	pruneSleepTime   time.Duration
	seenSemaphore     *semaphore.Weighted
	seenSemaphoreSize int64
}
type Logger interface {
	AddBlockStream(context.Context, *types.Block) error
	RemoveBlockStream(context.Context, *types.BlockIdentifier) error
}
type PruneHelper interface {
	PruneableIndex(ctx context.Context, headIndex int64) (int64, error)
}
func New(
	ctx context.Context,
	network *types.NetworkIdentifier,
	fetcher *fetcher.Fetcher,
	blockStorage *modules.BlockStorage,
	counterStorage *modules.CounterStorage,
	logger Logger,
	cancel context.CancelFunc,
	workers []modules.BlockWorker,
	options ...Option,
) *StatefulSyncer {
	s := &StatefulSyncer{
		network:        network,
		fetcher:        fetcher,
		cancel:         cancel,
		blockStorage:   blockStorage,
		counterStorage: counterStorage,
		workers:        workers,
		logger:         logger,
		cacheSize:         syncer.DefaultCacheSize,
		maxConcurrency:    syncer.DefaultMaxConcurrency,
		pastBlockLimit:    syncer.DefaultPastBlockLimit,
		adjustmentWindow:  syncer.DefaultAdjustmentWindow,
		pruneSleepTime:    DefaultPruneSleepTime,
		seenSemaphoreSize: int64(runtime.NumCPU()),
	}
	for _, opt := range options {
		opt(s)
	}
	s.seenSemaphore = semaphore.NewWeighted(s.seenSemaphoreSize)
	return s
}
func (s *StatefulSyncer) Sync(ctx context.Context, startIndex int64, endIndex int64) error {
	s.blockStorage.Initialize(s.workers)
	if startIndex != -1 {  
		if err := s.blockStorage.SetNewStartIndex(ctx, startIndex); err != nil {
			return fmt.Errorf("%w: unable to set new start index", err)
		}
	} else {  
		head, err := s.blockStorage.GetHeadBlockIdentifier(ctx)
		if err == nil {
			startIndex = head.Index + 1
		}
	}
	pastBlocks := s.blockStorage.CreateBlockCache(ctx, s.pastBlockLimit)
	syncer := syncer.New(
		s.network,
		s,
		s,
		s.cancel,
		syncer.WithPastBlocks(pastBlocks),
		syncer.WithCacheSize(s.cacheSize),
		syncer.WithMaxConcurrency(s.maxConcurrency),
		syncer.WithAdjustmentWindow(s.adjustmentWindow),
	)
	return syncer.Sync(ctx, startIndex, endIndex)
}
func (s *StatefulSyncer) Prune(ctx context.Context, helper PruneHelper) error {
	for ctx.Err() == nil {
		if err := utils.ContextSleep(ctx, s.pruneSleepTime); err != nil {
			return err
		}
		headBlock, err := s.blockStorage.GetHeadBlockIdentifier(ctx)
		if headBlock == nil && errors.Is(err, storageErrs.ErrHeadBlockNotFound) {
			continue
		}
		if err != nil {
			return err
		}
		oldestIndex, err := s.blockStorage.GetOldestBlockIndex(ctx)
		if oldestIndex == -1 && errors.Is(err, storageErrs.ErrOldestIndexMissing) {
			continue
		}
		if err != nil {
			return err
		}
		pruneableIndex, err := helper.PruneableIndex(ctx, headBlock.Index)
		if err != nil {
			return fmt.Errorf("%w: could not determine pruneable index", err)
		}
		if pruneableIndex < oldestIndex {
			continue
		}
		firstPruned, lastPruned, err := s.blockStorage.Prune(
			ctx,
			pruneableIndex,
			int64(s.pastBlockLimit)*pruneBuffer,  
		)
		if err != nil {
			return err
		}
		if firstPruned != -1 && lastPruned != -1 {
			pruneMessage := fmt.Sprintf("pruned blocks %d-%d", firstPruned, lastPruned)
			if firstPruned == lastPruned {
				pruneMessage = fmt.Sprintf("pruned block %d", firstPruned)
			}
			log.Println(pruneMessage)
		}
	}
	return ctx.Err()
}
func (s *StatefulSyncer) BlockSeen(ctx context.Context, block *types.Block) error {
	if err := s.seenSemaphore.Acquire(ctx, semaphoreWeight); err != nil {
		return err
	}
	defer s.seenSemaphore.Release(semaphoreWeight)
	if err := s.blockStorage.SeeBlock(ctx, block); err != nil {
		return fmt.Errorf(
			"%w: unable to pre-store block %s:%d",
			err,
			block.BlockIdentifier.Hash,
			block.BlockIdentifier.Index,
		)
	}
	return nil
}
func (s *StatefulSyncer) BlockAdded(ctx context.Context, block *types.Block) error {
	err := s.blockStorage.AddBlock(ctx, block)
	if err != nil {
		return fmt.Errorf(
			"%w: unable to add block to storage %s:%d",
			err,
			block.BlockIdentifier.Hash,
			block.BlockIdentifier.Index,
		)
	}
	_ = s.logger.AddBlockStream(ctx, block)
	return nil
}
func (s *StatefulSyncer) BlockRemoved(
	ctx context.Context,
	blockIdentifier *types.BlockIdentifier,
) error {
	err := s.blockStorage.RemoveBlock(ctx, blockIdentifier)
	if err != nil {
		return fmt.Errorf(
			"%w: unable to remove block from storage %s:%d",
			err,
			blockIdentifier.Hash,
			blockIdentifier.Index,
		)
	}
	_ = s.logger.RemoveBlockStream(ctx, blockIdentifier)
	return nil
}