From ea8a347ece85e2445ae64717ac94ac8bbadb0370 Mon Sep 17 00:00:00 2001
From: Alex Sharov <AskAlexSharov@gmail.com>
Date: Thu, 28 Apr 2022 09:10:00 +0700
Subject: [PATCH] Downloader: calc stat inside, add --torrent.download.slots
 and limit downloads inside (#3986)

* save

* save

* save

* save

* save

* save

* save

* save

* save
---
 cmd/downloader/downloader/downloader.go       | 387 +++++++++---------
 cmd/downloader/downloader/grpc_server.go      | 118 ++----
 .../downloader/torrentcfg/torrentcfg.go       |  27 +-
 cmd/downloader/downloader/util.go             |  78 +++-
 cmd/downloader/main.go                        |  12 +-
 cmd/observer/observer/crawler.go              |   5 +-
 cmd/utils/flags.go                            |   6 +
 eth/backend.go                                |  10 +-
 eth/stagedsync/stage_headers.go               |  95 ++---
 go.mod                                        |  22 +-
 go.sum                                        |  99 +++--
 turbo/cli/default_flags.go                    |   1 +
 12 files changed, 457 insertions(+), 403 deletions(-)

diff --git a/cmd/downloader/downloader/downloader.go b/cmd/downloader/downloader/downloader.go
index ac417a7d3f..a28d58f603 100644
--- a/cmd/downloader/downloader/downloader.go
+++ b/cmd/downloader/downloader/downloader.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"runtime"
+	"sync"
 	"time"
 
 	"github.com/anacrolix/torrent"
@@ -13,14 +14,17 @@ import (
 	"github.com/ledgerwatch/erigon-lib/kv"
 	"github.com/ledgerwatch/erigon/cmd/downloader/downloader/torrentcfg"
 	"github.com/ledgerwatch/log/v3"
+	"golang.org/x/sync/semaphore"
 )
 
-const ASSERT = false
-
 type Protocols struct {
 	TorrentClient *torrent.Client
 	DB            kv.RwDB
 	cfg           *torrentcfg.Cfg
+
+	statsLock   *sync.RWMutex
+	stats       AggStats
+	snapshotDir *dir.Rw
 }
 
 func New(cfg *torrentcfg.Cfg, snapshotDir *dir.Rw) (*Protocols, error) {
@@ -43,6 +47,8 @@ func New(cfg *torrentcfg.Cfg, snapshotDir *dir.Rw) (*Protocols, error) {
 		cfg:           cfg,
 		TorrentClient: torrentClient,
 		DB:            cfg.DB,
+		statsLock:     &sync.RWMutex{},
+		snapshotDir:   snapshotDir,
 	}, nil
 }
 
@@ -66,245 +72,218 @@ func readPeerID(db kv.RoDB) (peerID []byte, err error) {
 	return peerID, nil
 }
 
-func (cli *Protocols) Close() {
-	for _, tr := range cli.TorrentClient.Torrents() {
-		tr.Drop()
-	}
-	cli.TorrentClient.Close()
-	cli.DB.Close()
-	if cli.cfg.CompletionCloser != nil {
-		cli.cfg.CompletionCloser.Close() //nolint
+func (cli *Protocols) Start(ctx context.Context, silent bool) error {
+	if err := BuildTorrentsAndAdd(ctx, cli.snapshotDir, cli.TorrentClient); err != nil {
+		return err
 	}
-}
 
-func (cli *Protocols) PeerID() []byte {
-	peerID := cli.TorrentClient.PeerID()
-	return peerID[:]
-}
+	var sem = semaphore.NewWeighted(int64(cli.cfg.DownloadSlots))
 
-func LoggingLoop(ctx context.Context, torrentClient *torrent.Client) {
-	interval := time.Second * 20
-	logEvery := time.NewTicker(interval)
-	defer logEvery.Stop()
-	var m runtime.MemStats
-	var stats AggStats
-
-	for {
-		select {
-		case <-ctx.Done():
-			return
-		case <-logEvery.C:
-			torrents := torrentClient.Torrents()
-			allComplete := true
-			gotInfo := 0
+	go func() {
+		for {
+			torrents := cli.TorrentClient.Torrents()
 			for _, t := range torrents {
-				select {
-				case <-t.GotInfo(): // all good
-					gotInfo++
-				default:
+				<-t.GotInfo()
+				if t.Complete.Bool() {
+					continue
 				}
-				allComplete = allComplete && t.Complete.Bool()
-			}
-			if gotInfo < len(torrents) {
-				log.Info(fmt.Sprintf("[torrent] Waiting for torrents metadata: %d/%d", gotInfo, len(torrents)))
-				continue
-			}
+				if err := sem.Acquire(ctx, 1); err != nil {
+					return
+				}
+				t.AllowDataDownload()
+				t.DownloadAll()
+				go func(t *torrent.Torrent) {
+					//r := t.NewReader()
+					//_, _ = io.Copy(io.Discard, r) // enable streaming - it will prioritize sequential download
 
-			runtime.ReadMemStats(&m)
-			stats = CalcStats(stats, interval, torrentClient)
-			if allComplete {
-				log.Info("[torrent] Seeding",
-					"download", common2.ByteCount(uint64(stats.readBytesPerSec))+"/s",
-					"upload", common2.ByteCount(uint64(stats.writeBytesPerSec))+"/s",
-					"unique_peers", stats.peersCount,
-					"files", stats.torrentsCount,
-					"alloc", common2.ByteCount(m.Alloc), "sys", common2.ByteCount(m.Sys))
-				continue
+					<-t.Complete.On()
+					sem.Release(1)
+				}(t)
 			}
+			time.Sleep(330 * time.Second)
+		}
+	}()
 
-			log.Info("[torrent] Downloading",
-				"Progress", fmt.Sprintf("%.2f%%", stats.Progress),
-				"download", common2.ByteCount(uint64(stats.readBytesPerSec))+"/s",
-				"upload", common2.ByteCount(uint64(stats.writeBytesPerSec))+"/s",
-				"unique_peers", stats.peersCount,
-				"files", stats.torrentsCount,
-				"alloc", common2.ByteCount(m.Alloc), "sys", common2.ByteCount(m.Sys))
-			if stats.peersCount == 0 {
-				ips := torrentClient.BadPeerIPs()
-				if len(ips) > 0 {
-					log.Info("[torrent] Stats", "banned", ips)
+	go func() {
+		var m runtime.MemStats
+		logEvery := time.NewTicker(20 * time.Second)
+		defer logEvery.Stop()
+
+		interval := 5 * time.Second
+		statEvery := time.NewTicker(interval)
+		defer statEvery.Stop()
+		for {
+			select {
+			case <-ctx.Done():
+				return
+			case <-statEvery.C:
+				cli.ReCalcStats(interval)
+
+			case <-logEvery.C:
+				if silent {
+					continue
 				}
 
+				stats := cli.Stats()
+
+				if stats.MetadataReady < stats.FilesTotal {
+					log.Info(fmt.Sprintf("[Snapshots] Waiting for torrents metadata: %d/%d", stats.MetadataReady, stats.FilesTotal))
+					continue
+				}
+
+				runtime.ReadMemStats(&m)
+				if stats.Completed {
+					log.Info("[Snapshots] Seeding",
+						"up", common2.ByteCount(stats.UploadRate)+"/s",
+						"peers", stats.PeersUnique,
+						"connections", stats.ConnectionsTotal,
+						"files", stats.FilesTotal,
+						"alloc", common2.ByteCount(m.Alloc), "sys", common2.ByteCount(m.Sys))
+					continue
+				}
+
+				log.Info("[Snapshots] Downloading",
+					"progress", fmt.Sprintf("%.2f%% %s/%s", stats.Progress, common2.ByteCount(stats.BytesCompleted), common2.ByteCount(stats.BytesTotal)),
+					"download", common2.ByteCount(stats.DownloadRate)+"/s",
+					"upload", common2.ByteCount(stats.UploadRate)+"/s",
+					"peers", stats.PeersUnique,
+					"connections", stats.ConnectionsTotal,
+					"files", stats.FilesTotal,
+					"alloc", common2.ByteCount(m.Alloc), "sys", common2.ByteCount(m.Sys))
+				if stats.PeersUnique == 0 {
+					ips := cli.TorrentClient.BadPeerIPs()
+					if len(ips) > 0 {
+						log.Info("[Snapshots] Stats", "banned", ips)
+					}
+				}
 			}
 		}
-	}
-}
-
-func (cli *Protocols) StopSeeding(hash metainfo.Hash) error {
-	t, ok := cli.TorrentClient.Torrent(hash)
-	if !ok {
-		return nil
-	}
-	ch := t.Closed()
-	t.Drop()
-	<-ch
+	}()
 	return nil
 }
 
-type AggStats struct {
-	readBytesPerSec  int64
-	writeBytesPerSec int64
-	peersCount       int64
-
-	Progress      float32
-	torrentsCount int
-
-	bytesRead    int64
-	bytesWritten int64
-}
+func (cli *Protocols) ReCalcStats(interval time.Duration) {
+	cli.statsLock.Lock()
+	defer cli.statsLock.Unlock()
+	prevStats, stats := cli.stats, cli.stats
 
-func CalcStats(prevStats AggStats, interval time.Duration, client *torrent.Client) (result AggStats) {
-	var aggBytesCompleted, aggLen int64
-	//var aggCompletedPieces, aggNumPieces, aggPartialPieces int
-	peers := map[torrent.PeerID]*torrent.PeerConn{}
-	torrents := client.Torrents()
-	connStats := client.ConnStats()
+	peers := make(map[torrent.PeerID]struct{}, 16)
+	torrents := cli.TorrentClient.Torrents()
+	connStats := cli.TorrentClient.ConnStats()
 
-	result.bytesRead += connStats.BytesReadUsefulIntendedData.Int64()
-	result.bytesWritten += connStats.BytesWrittenData.Int64()
+	stats.BytesRead = uint64(connStats.BytesReadUsefulIntendedData.Int64())
+	stats.BytesWritten = uint64(connStats.BytesWrittenData.Int64())
 
+	stats.BytesTotal, stats.BytesCompleted, stats.ConnectionsTotal, stats.MetadataReady = 0, 0, 0, 0
 	for _, t := range torrents {
-		aggBytesCompleted += t.BytesCompleted()
-		aggLen += t.Length()
-
-		for _, peer := range t.PeerConns() {
-			peers[peer.PeerID] = peer
+		select {
+		case <-t.GotInfo():
+			stats.MetadataReady++
+			for _, peer := range t.PeerConns() {
+				stats.ConnectionsTotal++
+				peers[peer.PeerID] = struct{}{}
+			}
+			stats.BytesCompleted += uint64(t.BytesCompleted())
+			stats.BytesTotal += uint64(t.Length())
+		default:
 		}
+
+		stats.Completed = stats.Completed && t.Complete.Bool()
 	}
 
-	result.readBytesPerSec += (result.bytesRead - prevStats.bytesRead) / int64(interval.Seconds())
-	result.writeBytesPerSec += (result.bytesWritten - prevStats.bytesWritten) / int64(interval.Seconds())
+	stats.DownloadRate = (stats.BytesRead - prevStats.BytesRead) / uint64(interval.Seconds())
+	stats.UploadRate = (stats.BytesWritten - prevStats.BytesWritten) / uint64(interval.Seconds())
 
-	result.Progress = float32(float64(100) * (float64(aggBytesCompleted) / float64(aggLen)))
+	if stats.BytesTotal == 0 {
+		stats.Progress = 0
+	} else {
+		stats.Progress = float32(float64(100) * (float64(stats.BytesCompleted) / float64(stats.BytesTotal)))
+		if stats.Progress == 100 && !stats.Completed {
+			stats.Progress = 99.99
+		}
+	}
+	stats.PeersUnique = int32(len(peers))
+	stats.FilesTotal = int32(len(torrents))
 
-	result.peersCount = int64(len(peers))
-	result.torrentsCount = len(torrents)
-	return result
+	cli.stats = stats
 }
 
-func AddTorrentFile(ctx context.Context, torrentFilePath string, torrentClient *torrent.Client) (mi *metainfo.MetaInfo, err error) {
-	mi, err = metainfo.LoadFromFile(torrentFilePath)
-	if err != nil {
-		return nil, err
-	}
-	mi.AnnounceList = Trackers
+func (cli *Protocols) Stats() AggStats {
+	cli.statsLock.RLock()
+	defer cli.statsLock.RUnlock()
+	return cli.stats
+}
 
-	t := time.Now()
-	_, err = torrentClient.AddTorrent(mi)
-	if err != nil {
-		return mi, err
-	}
-	took := time.Since(t)
-	if took > 3*time.Second {
-		log.Info("[torrent] Check validity", "file", torrentFilePath, "took", took)
+func (cli *Protocols) Close() {
+	//for _, tr := range cli.TorrentClient.Torrents() {
+	//	go func() {}()
+	//	fmt.Printf("alex: CLOse01: %s\n", tr.Name())
+	//	tr.DisallowDataUpload()
+	//	fmt.Printf("alex: CLOse02: %s\n", tr.Name())
+	//	tr.DisallowDataDownload()
+	//	fmt.Printf("alex: CLOse03: %s\n", tr.Name())
+	//  ch := t.Closed()
+	//	tr.Drop()
+	//  <-ch
+	//}
+	cli.TorrentClient.Close()
+	cli.DB.Close()
+	if cli.cfg.CompletionCloser != nil {
+		if err := cli.cfg.CompletionCloser.Close(); err != nil {
+			log.Warn("[Snapshots] CompletionCloser", "err", err)
+		}
 	}
-	return mi, nil
 }
 
-// AddTorrentFiles - adding .torrent files to torrentClient (and checking their hashes), if .torrent file
-// added first time - pieces verification process will start (disk IO heavy) - Progress
-// kept in `piece completion storage` (surviving reboot). Once it done - no disk IO needed again.
-// Don't need call torrent.VerifyData manually
-func AddTorrentFiles(ctx context.Context, snapshotsDir *dir.Rw, torrentClient *torrent.Client) error {
-	files, err := AllTorrentPaths(snapshotsDir.Path)
-	if err != nil {
-		return err
-	}
-	for _, torrentFilePath := range files {
-		if _, err := AddTorrentFile(ctx, torrentFilePath, torrentClient); err != nil {
-			return err
-		}
-		select {
-		case <-ctx.Done():
-			return ctx.Err()
-		default:
-		}
+func (cli *Protocols) PeerID() []byte {
+	peerID := cli.TorrentClient.PeerID()
+	return peerID[:]
+}
 
+func (cli *Protocols) StopSeeding(hash metainfo.Hash) error {
+	t, ok := cli.TorrentClient.Torrent(hash)
+	if !ok {
+		return nil
 	}
-
+	ch := t.Closed()
+	t.Drop()
+	<-ch
 	return nil
 }
 
-// ResolveAbsentTorrents - add hard-coded hashes (if client doesn't have) as magnet links and download everything
-func ResolveAbsentTorrents(ctx context.Context, torrentClient *torrent.Client, preverifiedHashes []metainfo.Hash, snapshotDir *dir.Rw, silent bool) error {
-	mi := &metainfo.MetaInfo{AnnounceList: Trackers}
-	for i := range preverifiedHashes {
-		if _, ok := torrentClient.Torrent(preverifiedHashes[i]); ok {
-			continue
-		}
-		magnet := mi.Magnet(&preverifiedHashes[i], nil)
-		t, err := torrentClient.AddMagnet(magnet.String())
-		if err != nil {
-			return err
-		}
-		t.AllowDataDownload()
-		t.AllowDataUpload()
-	}
-	if !silent {
-		ctxLocal, cancel := context.WithCancel(ctx)
-		defer cancel()
-		go LoggingLoop(ctxLocal, torrentClient)
-	}
+type AggStats struct {
+	MetadataReady, FilesTotal int32
+	PeersUnique               int32
+	ConnectionsTotal          uint64
 
-	for _, t := range torrentClient.Torrents() {
-		select {
-		case <-ctx.Done():
-			return ctx.Err()
-		case <-t.GotInfo():
-			if !t.Complete.Bool() {
-				t.DownloadAll()
-			}
-			mi := t.Metainfo()
-			if err := CreateTorrentFileIfNotExists(snapshotDir, t.Info(), &mi); err != nil {
-				return err
-			}
-		}
-	}
+	Completed bool
+	Progress  float32
 
-	return nil
-}
+	BytesCompleted, BytesTotal uint64
 
-//nolint
-func waitForChecksumVerify(ctx context.Context, torrentClient *torrent.Client) {
-	//TODO: tr.VerifyData() - find when to call it
-	ctx, cancel := context.WithCancel(ctx)
-	defer cancel()
-	go func() {
-		interval := time.Second * 5
-		logEvery := time.NewTicker(interval)
-		defer logEvery.Stop()
+	UploadRate, DownloadRate uint64
 
-		for {
-			select {
-			case <-ctx.Done():
-				return
-			case <-logEvery.C:
-				var aggBytesCompleted, aggLen int64
-				for _, t := range torrentClient.Torrents() {
-					aggBytesCompleted += t.BytesCompleted()
-					aggLen += t.Length()
-				}
+	BytesRead    uint64
+	BytesWritten uint64
+}
 
-				line := fmt.Sprintf(
-					"[torrent] verifying snapshots: %s/%s",
-					common2.ByteCount(uint64(aggBytesCompleted)),
-					common2.ByteCount(uint64(aggLen)),
-				)
-				log.Info(line)
-			}
-		}
-	}()
-	torrentClient.WaitAll() // wait for checksum verify
+// AddTorrentFile - adding .torrent file to torrentClient (and checking their hashes), if .torrent file
+// added first time - pieces verification process will start (disk IO heavy) - Progress
+// kept in `piece completion storage` (surviving reboot). Once it done - no disk IO needed again.
+// Don't need call torrent.VerifyData manually
+func AddTorrentFile(ctx context.Context, torrentFilePath string, torrentClient *torrent.Client) (*torrent.Torrent, error) {
+	mi, err := metainfo.LoadFromFile(torrentFilePath)
+	if err != nil {
+		return nil, err
+	}
+	mi.AnnounceList = Trackers
+	t, err := torrentClient.AddTorrent(mi)
+	if err != nil {
+		return nil, err
+	}
+	t.DisallowDataDownload()
+	t.AllowDataUpload()
+	return t, nil
 }
 
 func VerifyDtaFiles(ctx context.Context, snapshotDir string) error {
@@ -341,12 +320,12 @@ func VerifyDtaFiles(ctx context.Context, snapshotDir string) error {
 		err = verifyTorrent(&info, snapshotDir, func(i int, good bool) error {
 			j++
 			if !good {
-				log.Error("[torrent] Verify hash mismatch", "at piece", i, "file", f)
+				log.Error("[Snapshots] Verify hash mismatch", "at piece", i, "file", f)
 				return fmt.Errorf("invalid file")
 			}
 			select {
 			case <-logEvery.C:
-				log.Info("[torrent] Verify", "Progress", fmt.Sprintf("%.2f%%", 100*float64(j)/float64(totalPieces)))
+				log.Info("[Snapshots] Verify", "Progress", fmt.Sprintf("%.2f%%", 100*float64(j)/float64(totalPieces)))
 			case <-ctx.Done():
 				return ctx.Err()
 			default:
@@ -357,6 +336,6 @@ func VerifyDtaFiles(ctx context.Context, snapshotDir string) error {
 			return err
 		}
 	}
-	log.Info("[torrent] Verify succeed")
+	log.Info("[Snapshots] Verify succeed")
 	return nil
 }
diff --git a/cmd/downloader/downloader/grpc_server.go b/cmd/downloader/downloader/grpc_server.go
index 06db650ed4..cfb868f4b8 100644
--- a/cmd/downloader/downloader/grpc_server.go
+++ b/cmd/downloader/downloader/grpc_server.go
@@ -3,15 +3,14 @@ package downloader
 import (
 	"context"
 	"errors"
-	"path/filepath"
 
-	"github.com/anacrolix/torrent"
 	"github.com/anacrolix/torrent/metainfo"
 	"github.com/ledgerwatch/erigon-lib/common/dir"
 	"github.com/ledgerwatch/erigon-lib/gointerfaces"
 	proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader"
 	prototypes "github.com/ledgerwatch/erigon-lib/gointerfaces/types"
 	"github.com/ledgerwatch/erigon-lib/kv"
+	"github.com/ledgerwatch/log/v3"
 	"google.golang.org/protobuf/types/known/emptypb"
 )
 
@@ -23,108 +22,79 @@ var (
 	_ proto_downloader.DownloaderServer = &GrpcServer{}
 )
 
-func NewGrpcServer(db kv.RwDB, client *Protocols, snapshotDir *dir.Rw, silent bool) (*GrpcServer, error) {
+func NewGrpcServer(db kv.RwDB, client *Protocols, snapshotDir *dir.Rw) (*GrpcServer, error) {
 	sn := &GrpcServer{
 		db:          db,
 		t:           client,
 		snapshotDir: snapshotDir,
-		silent:      silent,
 	}
 	return sn, nil
 }
 
-func CreateTorrentFilesAndAdd(ctx context.Context, snapshotDir *dir.Rw, torrentClient *torrent.Client) error {
-	if err := BuildTorrentFilesIfNeed(ctx, snapshotDir); err != nil {
-		return err
-	}
-	if err := AddTorrentFiles(ctx, snapshotDir, torrentClient); err != nil {
-		return err
-	}
-	for _, t := range torrentClient.Torrents() {
-		t.AllowDataUpload()
-		if !t.Complete.Bool() {
-			t.AllowDataDownload()
-			t.DownloadAll()
-		}
-	}
-	return nil
-}
-
 type GrpcServer struct {
 	proto_downloader.UnimplementedDownloaderServer
 	t           *Protocols
 	db          kv.RwDB
 	snapshotDir *dir.Rw
-	silent      bool
 }
 
 func (s *GrpcServer) Download(ctx context.Context, request *proto_downloader.DownloadRequest) (*emptypb.Empty, error) {
-	infoHashes := make([]metainfo.Hash, len(request.Items))
-	for i, it := range request.Items {
+	torrentClient := s.t.TorrentClient
+	mi := &metainfo.MetaInfo{AnnounceList: Trackers}
+	for _, it := range request.Items {
 		if it.TorrentHash == nil {
-			if err := BuildTorrentFileIfNeed(ctx, it.Path, s.snapshotDir); err != nil {
-				return nil, err
-			}
-			metaInfo, err := AddTorrentFile(ctx, filepath.Join(s.snapshotDir.Path, it.Path+".torrent"), s.t.TorrentClient)
+			_, err := BuildTorrentAndAdd(ctx, it.Path, s.snapshotDir, s.t.TorrentClient)
 			if err != nil {
 				return nil, err
 			}
-			infoHashes[i] = metaInfo.HashInfoBytes()
-		} else {
-			infoHashes[i] = gointerfaces.ConvertH160toAddress(it.TorrentHash)
+			continue
 		}
-	}
-	if err := ResolveAbsentTorrents(ctx, s.t.TorrentClient, infoHashes, s.snapshotDir, s.silent); err != nil {
-		return nil, err
-	}
-	for _, t := range s.t.TorrentClient.Torrents() {
-		t.AllowDataDownload()
-		t.AllowDataUpload()
-		if !t.Complete.Bool() {
-			t.DownloadAll()
+
+		hash := Proto2InfoHash(it.TorrentHash)
+		if _, ok := torrentClient.Torrent(hash); ok {
+			continue
 		}
+
+		magnet := mi.Magnet(&hash, nil)
+		go func(magnetUrl string) {
+			t, err := torrentClient.AddMagnet(magnetUrl)
+			if err != nil {
+				log.Warn("[downloader] add magnet link", "err", err)
+				return
+			}
+			t.DisallowDataDownload()
+			t.AllowDataUpload()
+			<-t.GotInfo()
+			mi := t.Metainfo()
+			if err := CreateTorrentFileIfNotExists(s.snapshotDir, t.Info(), &mi); err != nil {
+				log.Warn("[downloader] create torrent file", "err", err)
+				return
+			}
+		}(magnet.String())
+
 	}
 	return &emptypb.Empty{}, nil
 }
 
 func (s *GrpcServer) Stats(ctx context.Context, request *proto_downloader.StatsRequest) (*proto_downloader.StatsReply, error) {
-	torrents := s.t.TorrentClient.Torrents()
-	reply := &proto_downloader.StatsReply{Completed: true, Torrents: int32(len(torrents))}
+	stats := s.t.Stats()
+	return &proto_downloader.StatsReply{
+		MetadataReady: stats.MetadataReady,
+		FilesTotal:    stats.FilesTotal,
 
-	peers := map[torrent.PeerID]struct{}{}
+		Completed: stats.Completed,
+		Progress:  stats.Progress,
 
-	for _, t := range torrents {
-		select {
-		case <-ctx.Done():
-			return nil, ctx.Err()
-		case <-t.GotInfo():
-			reply.BytesCompleted += uint64(t.BytesCompleted())
-			reply.BytesTotal += uint64(t.Info().TotalLength())
-			reply.Completed = reply.Completed && t.Complete.Bool()
-			reply.Connections += uint64(len(t.PeerConns()))
+		PeersUnique:      stats.PeersUnique,
+		ConnectionsTotal: stats.ConnectionsTotal,
 
-			for _, peer := range t.PeerConns() {
-				peers[peer.PeerID] = struct{}{}
-			}
-		default:
-			reply.Completed = false
-		}
-	}
-
-	reply.Peers = int32(len(peers))
-	reply.Progress = int32(100 * (float64(reply.BytesCompleted) / float64(reply.BytesTotal)))
-	if reply.Progress == 100 && !reply.Completed {
-		reply.Progress = 99
-	}
-	return reply, nil
+		BytesCompleted: stats.BytesCompleted,
+		BytesTotal:     stats.BytesTotal,
+		UploadRate:     stats.UploadRate,
+		DownloadRate:   stats.DownloadRate,
+	}, nil
 }
 
-func Proto2InfoHashes(in []*prototypes.H160) []metainfo.Hash {
-	infoHashes := make([]metainfo.Hash, len(in))
-	i := 0
-	for _, h := range in {
-		infoHashes[i] = gointerfaces.ConvertH160toAddress(h)
-		i++
-	}
-	return infoHashes
+func Proto2InfoHash(in *prototypes.H160) metainfo.Hash {
+	return gointerfaces.ConvertH160toAddress(in)
 }
diff --git a/cmd/downloader/downloader/torrentcfg/torrentcfg.go b/cmd/downloader/downloader/torrentcfg/torrentcfg.go
index 6ce8d71b27..d42d16d604 100644
--- a/cmd/downloader/downloader/torrentcfg/torrentcfg.go
+++ b/cmd/downloader/downloader/torrentcfg/torrentcfg.go
@@ -25,6 +25,7 @@ type Cfg struct {
 	*torrent.ClientConfig
 	DB               kv.RwDB
 	CompletionCloser io.Closer
+	DownloadSlots    int
 }
 
 func Default() *torrent.ClientConfig {
@@ -33,10 +34,10 @@ func Default() *torrent.ClientConfig {
 	// enable dht
 	torrentConfig.NoDHT = true
 	//torrentConfig.DisableTrackers = true
-	//torrentConfig.DisableWebtorrent = true
+	torrentConfig.DisableWebtorrent = true
 	//torrentConfig.DisableWebseeds = true
 
-	// Increase default timeouts, because we often run on commodity networks
+	// Reduce defaults - to avoid peers with very bad geography
 	torrentConfig.MinDialTimeout = 1 * time.Second      // default: 3sec
 	torrentConfig.NominalDialTimeout = 10 * time.Second // default: 20sec
 	torrentConfig.HandshakesTimeout = 1 * time.Second   // default: 4sec
@@ -44,14 +45,15 @@ func Default() *torrent.ClientConfig {
 	return torrentConfig
 }
 
-func New(snapshotsDir *dir.Rw, verbosity lg.Level, natif nat.Interface, downloadRate, uploadRate datasize.ByteSize, port, maxPeers, connsPerFile int, db kv.RwDB) (*Cfg, error) {
+func New(snapshotsDir *dir.Rw, verbosity lg.Level, natif nat.Interface, downloadRate, uploadRate datasize.ByteSize, port, maxPeers, connsPerFile int, db kv.RwDB, downloadSlots int) (*Cfg, error) {
 	torrentConfig := Default()
 	// We would-like to reduce amount of goroutines in Erigon, so reducing next params
-	torrentConfig.EstablishedConnsPerTorrent = connsPerFile // default: 50
-	torrentConfig.TorrentPeersHighWater = maxPeers          // default: 500
-	torrentConfig.TorrentPeersLowWater = 50                 // default: 50
-	torrentConfig.HalfOpenConnsPerTorrent = 25              // default: 25
-	torrentConfig.TotalHalfOpenConns = 50                   // default: 100
+	torrentConfig.EstablishedConnsPerTorrent = connsPerFile       // default: 50
+	torrentConfig.HalfOpenConnsPerTorrent = min(25, connsPerFile) // default: 25
+	torrentConfig.TotalHalfOpenConns = 50                         // default: 100
+
+	torrentConfig.TorrentPeersHighWater = maxPeers         // default: 500
+	torrentConfig.TorrentPeersLowWater = min(50, maxPeers) // default: 50
 
 	torrentConfig.ListenPort = port
 	torrentConfig.Seed = true
@@ -100,5 +102,12 @@ func New(snapshotsDir *dir.Rw, verbosity lg.Level, natif nat.Interface, download
 	}
 	m := storage.NewMMapWithCompletion(snapshotsDir.Path, c)
 	torrentConfig.DefaultStorage = m
-	return &Cfg{ClientConfig: torrentConfig, DB: db, CompletionCloser: m}, nil
+	return &Cfg{ClientConfig: torrentConfig, DB: db, CompletionCloser: m, DownloadSlots: downloadSlots}, nil
+}
+
+func min(a, b int) int {
+	if a < b {
+		return a
+	}
+	return b
 }
diff --git a/cmd/downloader/downloader/util.go b/cmd/downloader/downloader/util.go
index d06e466c5c..4cdb11d6d1 100644
--- a/cmd/downloader/downloader/util.go
+++ b/cmd/downloader/downloader/util.go
@@ -9,9 +9,11 @@ import (
 	"io"
 	"os"
 	"path/filepath"
+	"runtime"
 	"sync"
 	"time"
 
+	"github.com/anacrolix/torrent"
 	"github.com/anacrolix/torrent/bencode"
 	"github.com/anacrolix/torrent/metainfo"
 	"github.com/anacrolix/torrent/mmap_span"
@@ -21,11 +23,12 @@ import (
 	"github.com/ledgerwatch/erigon/cmd/downloader/trackers"
 	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/ledgerwatch/log/v3"
+	"golang.org/x/sync/semaphore"
 )
 
 // Trackers - break down by priority tier
 var Trackers = [][]string{
-	trackers.First(10, trackers.Best),
+	trackers.First(7, trackers.Best),
 	//trackers.First(3, trackers.Udp),
 	//trackers.First(3, trackers.Https),
 	//trackers.First(10, trackers.Ws),
@@ -107,7 +110,10 @@ func BuildTorrentFileIfNeed(ctx context.Context, originalFileName string, root *
 		if !errors.Is(err, os.ErrNotExist) {
 			return err
 		}
-		info, err := BuildInfoBytesForFile(root.Path, originalFileName)
+		info := &metainfo.Info{PieceLength: torrentcfg.DefaultPieceSize}
+		if err := info.BuildFromFilePath(filepath.Join(root.Path, originalFileName)); err != nil {
+			return err
+		}
 		if err != nil {
 			return err
 		}
@@ -118,12 +124,24 @@ func BuildTorrentFileIfNeed(ctx context.Context, originalFileName string, root *
 	return nil
 }
 
+func BuildTorrentAndAdd(ctx context.Context, originalFileName string, snapshotDir *dir.Rw, client *torrent.Client) (*torrent.Torrent, error) {
+	if err := BuildTorrentFileIfNeed(ctx, originalFileName, snapshotDir); err != nil {
+		return nil, err
+	}
+	torrentFilePath := filepath.Join(snapshotDir.Path, originalFileName+".torrent")
+	t, err := AddTorrentFile(ctx, torrentFilePath, client)
+	if err != nil {
+		return nil, err
+	}
+	return t, nil
+}
+
 // BuildTorrentFilesIfNeed - create .torrent files from .seg files (big IO) - if .seg files were added manually
-func BuildTorrentFilesIfNeed(ctx context.Context, root *dir.Rw) error {
+func BuildTorrentFilesIfNeed(ctx context.Context, snapshotDir *dir.Rw) error {
 	logEvery := time.NewTicker(20 * time.Second)
 	defer logEvery.Stop()
 
-	files, err := allSegmentFiles(root.Path)
+	files, err := allSegmentFiles(snapshotDir.Path)
 	if err != nil {
 		return err
 	}
@@ -133,14 +151,14 @@ func BuildTorrentFilesIfNeed(ctx context.Context, root *dir.Rw) error {
 		wg.Add(1)
 		go func(f string, i int) {
 			defer wg.Done()
-			errs <- BuildTorrentFileIfNeed(ctx, f, root)
+			errs <- BuildTorrentFileIfNeed(ctx, f, snapshotDir)
 
 			select {
 			default:
 			case <-ctx.Done():
 				errs <- ctx.Err()
 			case <-logEvery.C:
-				log.Info("[torrent] Creating .torrent files", "Progress", fmt.Sprintf("%d/%d", i, len(files)))
+				log.Info("[Snapshots] Creating .torrent files", "Progress", fmt.Sprintf("%d/%d", i, len(files)))
 			}
 		}(f, i)
 	}
@@ -156,12 +174,50 @@ func BuildTorrentFilesIfNeed(ctx context.Context, root *dir.Rw) error {
 	return nil
 }
 
-func BuildInfoBytesForFile(root string, fileName string) (*metainfo.Info, error) {
-	info := &metainfo.Info{PieceLength: torrentcfg.DefaultPieceSize}
-	if err := info.BuildFromFilePath(filepath.Join(root, fileName)); err != nil {
-		return nil, err
+// BuildTorrentsAndAdd - create .torrent files from .seg files (big IO) - if .seg files were placed manually to snapshotDir
+func BuildTorrentsAndAdd(ctx context.Context, snapshotDir *dir.Rw, client *torrent.Client) error {
+	logEvery := time.NewTicker(20 * time.Second)
+	defer logEvery.Stop()
+	files, err := allSegmentFiles(snapshotDir.Path)
+	if err != nil {
+		return err
+	}
+	errs := make(chan error, len(files)*2)
+	wg := &sync.WaitGroup{}
+	workers := runtime.GOMAXPROCS(-1) - 1
+	if workers < 1 {
+		workers = 1
+	}
+	var sem = semaphore.NewWeighted(int64(workers))
+	for i, f := range files {
+		wg.Add(1)
+		if err := sem.Acquire(ctx, 1); err != nil {
+			return err
+		}
+		go func(f string, i int) {
+			defer sem.Release(1)
+			defer wg.Done()
+
+			select {
+			case <-ctx.Done():
+				errs <- ctx.Err()
+			default:
+			}
+
+			_, err := BuildTorrentAndAdd(ctx, f, snapshotDir, client)
+			errs <- err
+		}(f, i)
 	}
-	return info, nil
+	go func() {
+		wg.Wait()
+		close(errs)
+	}()
+	for err := range errs {
+		if err != nil {
+			return err
+		}
+	}
+	return nil
 }
 
 func CreateTorrentFileIfNotExists(root *dir.Rw, info *metainfo.Info, mi *metainfo.MetaInfo) error {
diff --git a/cmd/downloader/main.go b/cmd/downloader/main.go
index 95c1fcafaa..8cfeb52b58 100644
--- a/cmd/downloader/main.go
+++ b/cmd/downloader/main.go
@@ -43,6 +43,7 @@ var (
 	natSetting                     string
 	torrentVerbosity               string
 	downloadRateStr, uploadRateStr string
+	torrentDownloadSlots           int
 	torrentPort                    int
 	torrentMaxPeers                int
 	torrentConnsPerFile            int
@@ -63,6 +64,7 @@ func init() {
 	rootCmd.Flags().IntVar(&torrentPort, "torrent.port", utils.TorrentPortFlag.Value, utils.TorrentPortFlag.Usage)
 	rootCmd.Flags().IntVar(&torrentMaxPeers, "torrent.maxpeers", utils.TorrentMaxPeersFlag.Value, utils.TorrentMaxPeersFlag.Usage)
 	rootCmd.Flags().IntVar(&torrentConnsPerFile, "torrent.conns.perfile", utils.TorrentConnsPerFileFlag.Value, utils.TorrentConnsPerFileFlag.Usage)
+	rootCmd.Flags().IntVar(&torrentDownloadSlots, "torrent.download.slots", utils.TorrentDownloadSlotsFlag.Value, utils.TorrentDownloadSlotsFlag.Usage)
 
 	withDataDir(printTorrentHashes)
 	printTorrentHashes.PersistentFlags().BoolVar(&forceRebuild, "rebuild", false, "Force re-create .torrent files")
@@ -148,7 +150,7 @@ func Downloader(ctx context.Context) error {
 		return err
 	}
 
-	cfg, err := torrentcfg.New(snapshotDir, torrentLogLevel, natif, downloadRate, uploadRate, torrentPort, torrentMaxPeers, torrentConnsPerFile, db)
+	cfg, err := torrentcfg.New(snapshotDir, torrentLogLevel, natif, downloadRate, uploadRate, torrentPort, torrentMaxPeers, torrentConnsPerFile, db, torrentDownloadSlots)
 	if err != nil {
 		return err
 	}
@@ -160,13 +162,11 @@ func Downloader(ctx context.Context) error {
 	}
 	defer protocols.Close()
 	log.Info("[torrent] Start", "my peerID", fmt.Sprintf("%x", protocols.TorrentClient.PeerID()))
-	if err = downloader.CreateTorrentFilesAndAdd(ctx, snapshotDir, protocols.TorrentClient); err != nil {
-		return fmt.Errorf("CreateTorrentFilesAndAdd: %w", err)
+	if err := protocols.Start(ctx, false); err != nil {
+		return err
 	}
 
-	go downloader.LoggingLoop(ctx, protocols.TorrentClient)
-
-	bittorrentServer, err := downloader.NewGrpcServer(protocols.DB, protocols, snapshotDir, true)
+	bittorrentServer, err := downloader.NewGrpcServer(protocols.DB, protocols, snapshotDir)
 	if err != nil {
 		return fmt.Errorf("new server: %w", err)
 	}
diff --git a/cmd/observer/observer/crawler.go b/cmd/observer/observer/crawler.go
index 07005dc368..fc1c02d954 100644
--- a/cmd/observer/observer/crawler.go
+++ b/cmd/observer/observer/crawler.go
@@ -5,6 +5,9 @@ import (
 	"crypto/ecdsa"
 	"errors"
 	"fmt"
+	"sync/atomic"
+	"time"
+
 	"github.com/ledgerwatch/erigon/cmd/observer/database"
 	"github.com/ledgerwatch/erigon/cmd/observer/utils"
 	"github.com/ledgerwatch/erigon/core/forkid"
@@ -12,8 +15,6 @@ import (
 	"github.com/ledgerwatch/erigon/params"
 	"github.com/ledgerwatch/log/v3"
 	"golang.org/x/sync/semaphore"
-	"sync/atomic"
-	"time"
 )
 
 type Crawler struct {
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 9c805d7c50..e58cccb147 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -648,6 +648,11 @@ var (
 		Value: "4mb",
 		Usage: "bytes per second, example: 32mb",
 	}
+	TorrentDownloadSlotsFlag = cli.IntFlag{
+		Name:  "torrent.download.slots",
+		Value: 3,
+		Usage: "amount of files to download in parallel. If network has enough seeders 1-3 slot enough, if network has lack of seeders increase to 5-7 (too big value will slow down everything).",
+	}
 	TorrentPortFlag = cli.IntFlag{
 		Name:  "torrent.port",
 		Value: 42069,
@@ -1399,6 +1404,7 @@ func SetEthConfig(ctx *cli.Context, nodeConfig *node.Config, cfg *ethconfig.Conf
 			ctx.GlobalInt(TorrentMaxPeersFlag.Name),
 			ctx.GlobalInt(TorrentConnsPerFileFlag.Name),
 			db,
+			ctx.GlobalInt(TorrentDownloadSlotsFlag.Name),
 		)
 		if err != nil {
 			panic(err)
diff --git a/eth/backend.go b/eth/backend.go
index 7e946ce153..58caf70018 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -21,7 +21,6 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"google.golang.org/protobuf/types/known/emptypb"
 	"math/big"
 	"os"
 	"path/filepath"
@@ -30,6 +29,8 @@ import (
 	"sync"
 	"time"
 
+	"google.golang.org/protobuf/types/known/emptypb"
+
 	"github.com/holiman/uint256"
 	libcommon "github.com/ledgerwatch/erigon-lib/common"
 	"github.com/ledgerwatch/erigon-lib/direct"
@@ -324,10 +325,11 @@ func New(stack *node.Node, config *ethconfig.Config, txpoolCfg txpool2.Config, l
 			if err != nil {
 				return nil, err
 			}
-			if err = downloader.CreateTorrentFilesAndAdd(ctx, config.SnapshotDir, backend.downloadProtocols.TorrentClient); err != nil {
-				return nil, fmt.Errorf("CreateTorrentFilesAndAdd: %w", err)
+			if err := backend.downloadProtocols.Start(ctx, true); err != nil {
+				return nil, err
 			}
-			bittorrentServer, err := downloader.NewGrpcServer(backend.downloadProtocols.DB, backend.downloadProtocols, config.SnapshotDir, false)
+
+			bittorrentServer, err := downloader.NewGrpcServer(backend.downloadProtocols.DB, backend.downloadProtocols, config.SnapshotDir)
 			if err != nil {
 				return nil, fmt.Errorf("new server: %w", err)
 			}
diff --git a/eth/stagedsync/stage_headers.go b/eth/stagedsync/stage_headers.go
index 549dca13a9..491f680d75 100644
--- a/eth/stagedsync/stage_headers.go
+++ b/eth/stagedsync/stage_headers.go
@@ -1193,71 +1193,60 @@ func DownloadAndIndexSnapshotsIfNeed(s *StageState, ctx context.Context, tx kv.R
 // for MVP we sync with Downloader only once, in future will send new snapshots also
 func WaitForDownloader(ctx context.Context, tx kv.RwTx, cfg HeadersCfg) error {
 	snapshotsCfg := snapshothashes.KnownConfig(cfg.chainConfig.ChainName)
-	checkStatsEvery := time.NewTicker(5 * time.Second)
-	defer checkStatsEvery.Stop()
 
 	// send all hashes to the Downloader service
 	preverified := snapshotsCfg.Preverified
-	var prevBytesCompleted uint64
-	logEvery := time.NewTicker(logInterval)
-	defer logEvery.Stop()
+	req := &proto_downloader.DownloadRequest{Items: make([]*proto_downloader.DownloadItem, len(preverified))}
+	i := 0
 	for _, p := range preverified {
-		req := &proto_downloader.DownloadRequest{Items: make([]*proto_downloader.DownloadItem, 1)}
-		req.Items[0] = &proto_downloader.DownloadItem{
+		req.Items[i] = &proto_downloader.DownloadItem{
 			TorrentHash: downloadergrpc.String2Proto(p.Hash),
 			Path:        p.Name,
 		}
-		for {
-			select {
-			case <-ctx.Done():
-				return ctx.Err()
-			default:
-			}
-			if _, err := cfg.snapshotDownloader.Download(ctx, req); err != nil {
-				log.Error("[Snapshots] Can't call downloader", "err", err)
-				time.Sleep(10 * time.Second)
-				continue
-			}
-			break
+		i++
+	}
+	log.Info("[Snapshots] Fetching torrent files metadata")
+	for {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		default:
 		}
-
-		if reply, err := cfg.snapshotDownloader.Stats(ctx, &proto_downloader.StatsRequest{}); err != nil {
-			log.Warn("Error while waiting for snapshots progress", "err", err)
-		} else if reply.Completed {
+		if _, err := cfg.snapshotDownloader.Download(ctx, req); err != nil {
+			log.Error("[Snapshots] call downloader", "err", err)
+			time.Sleep(10 * time.Second)
 			continue
 		}
+		break
+	}
+	logEvery := time.NewTicker(logInterval / 3)
+	defer logEvery.Stop()
 
-		// Print download progress until all segments are available
-	Loop:
-		for {
-			select {
-			case <-ctx.Done():
-				return ctx.Err()
-			case <-checkStatsEvery.C:
-				if reply, err := cfg.snapshotDownloader.Stats(ctx, &proto_downloader.StatsRequest{}); err != nil {
-					log.Warn("Error while waiting for snapshots progress", "err", err)
-				} else if reply.Completed {
-					break Loop
-				}
-			case <-logEvery.C:
-				if reply, err := cfg.snapshotDownloader.Stats(ctx, &proto_downloader.StatsRequest{}); err != nil {
-					log.Warn("Error while waiting for snapshots progress", "err", err)
-				} else if reply.Completed {
-					break Loop
-				} else {
-					readBytesPerSec := (reply.BytesCompleted - prevBytesCompleted) / uint64(logInterval.Seconds())
-					// writeBytesPerSec += (reply.BytesWritten - prevBytesWritten) / int64(logInterval.Seconds())
-
-					//readiness := 100 * (float64(reply.BytesCompleted) / float64(reply.BytesTotal))
-					log.Info("[Snapshots] download", //"progress", fmt.Sprintf("%.2f%%", readiness),
-						"progress", libcommon.ByteCount(reply.BytesCompleted),
-						"download", libcommon.ByteCount(readBytesPerSec)+"/s",
-						"torrent_peers", reply.Peers,
-						"connections", reply.Connections,
-						// "upload", libcommon.ByteCount(writeBytesPerSec)+"/s",
-					)
-					prevBytesCompleted = reply.BytesCompleted
+	// Print download progress until all segments are available
+Loop:
+	for {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-logEvery.C:
+			if stats, err := cfg.snapshotDownloader.Stats(ctx, &proto_downloader.StatsRequest{}); err != nil {
+				log.Warn("Error while waiting for snapshots progress", "err", err)
+			} else if stats.Completed {
+				break Loop
+			} else {
+				if stats.MetadataReady < stats.FilesTotal {
+					log.Info(fmt.Sprintf("[Snapshots] Waiting for torrents metadata: %d/%d", stats.MetadataReady, stats.FilesTotal))
+					continue
 				}
+
+				log.Info("[Snapshots] download",
+					"progress", fmt.Sprintf("%.2f%% %s/%s", stats.Progress, libcommon.ByteCount(stats.BytesCompleted), libcommon.ByteCount(stats.BytesTotal)),
+					"download", libcommon.ByteCount(stats.DownloadRate)+"/s",
+					"upload", libcommon.ByteCount(stats.UploadRate)+"/s",
+					"peers", stats.PeersUnique,
+					"connections", stats.ConnectionsTotal,
+					"files", stats.FilesTotal,
+				)
 			}
 		}
 	}
diff --git a/go.mod b/go.mod
index 2b206802ae..aa641ae8c2 100644
--- a/go.mod
+++ b/go.mod
@@ -35,14 +35,14 @@ require (
 	github.com/json-iterator/go v1.1.12
 	github.com/julienschmidt/httprouter v1.3.0
 	github.com/kevinburke/go-bindata v3.21.0+incompatible
-	github.com/ledgerwatch/erigon-lib v0.0.0-20220425125405-5dc5ab7fe16b
+	github.com/ledgerwatch/erigon-lib v0.0.0-20220427024056-791f257bd412
 	github.com/ledgerwatch/log/v3 v3.4.1
 	github.com/ledgerwatch/secp256k1 v1.0.0
 	github.com/pelletier/go-toml v1.9.5
 	github.com/pelletier/go-toml/v2 v2.0.0-beta.8
 	github.com/quasilyte/go-ruleguard/dsl v0.3.19
 	github.com/rs/cors v1.8.2
-	github.com/shirou/gopsutil/v3 v3.22.2
+	github.com/shirou/gopsutil/v3 v3.22.3
 	github.com/spf13/cobra v1.4.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.7.1
@@ -61,12 +61,12 @@ require (
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
 	golang.org/x/time v0.0.0-20220411224347-583f2d630306
-	google.golang.org/grpc v1.45.0
+	google.golang.org/grpc v1.46.0
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
 	google.golang.org/protobuf v1.28.0
 	gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b
 	gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6
-	modernc.org/sqlite v1.14.2-0.20211125151325-d4ed92c0a70f
+	modernc.org/sqlite v1.17.0
 	pgregory.net/rapid v0.4.7
 )
 
@@ -150,26 +150,26 @@ require (
 	github.com/spaolacci/murmur3 v1.1.0 // indirect
 	github.com/syndtr/goleveldb v1.0.0 // indirect
 	github.com/tidwall/btree v0.7.2-0.20211211132910-4215444137fc // indirect
-	github.com/tklauser/go-sysconf v0.3.9 // indirect
-	github.com/tklauser/numcpus v0.3.0 // indirect
+	github.com/tklauser/go-sysconf v0.3.10 // indirect
+	github.com/tklauser/numcpus v0.4.0 // indirect
 	github.com/valyala/fastrand v1.1.0 // indirect
 	github.com/valyala/histogram v1.2.0 // indirect
 	github.com/yusufpapurcu/wmi v1.2.2 // indirect
 	go.etcd.io/bbolt v1.3.6 // indirect
 	golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
-	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
+	golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/tools v0.1.10 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 	lukechampine.com/uint128 v1.1.1 // indirect
-	modernc.org/cc/v3 v3.35.18 // indirect
-	modernc.org/ccgo/v3 v3.12.73 // indirect
-	modernc.org/libc v1.11.82 // indirect
+	modernc.org/cc/v3 v3.35.26 // indirect
+	modernc.org/ccgo/v3 v3.16.2 // indirect
+	modernc.org/libc v1.15.0 // indirect
 	modernc.org/mathutil v1.4.1 // indirect
-	modernc.org/memory v1.0.5 // indirect
+	modernc.org/memory v1.0.7 // indirect
 	modernc.org/opt v0.1.1 // indirect
 	modernc.org/strutil v1.1.1 // indirect
 	modernc.org/token v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 728394e6f6..12521e00b2 100644
--- a/go.sum
+++ b/go.sum
@@ -174,8 +174,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@@ -217,7 +217,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fjl/gencodec v0.0.0-20191126094850-e283372f291f h1:Y/gg/utVetS+WS6htAKCTDralkm/8hLIIUAtLFdbdQ8=
@@ -454,8 +454,8 @@ github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758 h1:0D5M2HQSGD3P
 github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
 github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
-github.com/ledgerwatch/erigon-lib v0.0.0-20220425125405-5dc5ab7fe16b h1:OhhQT6ynUpkFqfisRCRDkTFhGd2+8Hom7jKDey6O17w=
-github.com/ledgerwatch/erigon-lib v0.0.0-20220425125405-5dc5ab7fe16b/go.mod h1:0VKhW10UjEr7I6DaV+N0KNbXvMygC99qmRl2CfaN9gw=
+github.com/ledgerwatch/erigon-lib v0.0.0-20220427024056-791f257bd412 h1:IAh4RBG0kx8ZtwbZBfwcj6L5iGR0X1orIgeStOdpH7Y=
+github.com/ledgerwatch/erigon-lib v0.0.0-20220427024056-791f257bd412/go.mod h1:Z6hOzGMPdbzDcCs+EV5CEl/a6zOpgXqXL0K5956iXUc=
 github.com/ledgerwatch/log/v3 v3.4.1 h1:/xGwlVulXnsO9Uq+tzaExc8OWmXXHU0dnLalpbnY5Bc=
 github.com/ledgerwatch/log/v3 v3.4.1/go.mod h1:VXcz6Ssn6XEeU92dCMc39/g1F0OYAjw1Mt+dGP5DjXY=
 github.com/ledgerwatch/secp256k1 v1.0.0 h1:Usvz87YoTG0uePIV8woOof5cQnLXGYa162rFf3YnwaQ=
@@ -476,8 +476,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
-github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
+github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -656,8 +656,8 @@ github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5P
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
-github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
+github.com/shirou/gopsutil/v3 v3.22.3 h1:UebRzEomgMpv61e3hgD1tGooqX5trFbdU/ehphbHd00=
+github.com/shirou/gopsutil/v3 v3.22.3/go.mod h1:D01hZJ4pVHPpCTZ3m3T2+wDF2YAGfd+H4ifUguaQzHM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -704,10 +704,10 @@ github.com/tidwall/btree v0.7.2-0.20211211132910-4215444137fc/go.mod h1:LGm8L/DZ
 github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
-github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
-github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
-github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
-github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
+github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
+github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
+github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
+github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/torquem-ch/mdbx-go v0.23.2 h1:7axXl0leix2v8No+mRzeTV32hJrV1817aKhh+hTEpC8=
 github.com/torquem-ch/mdbx-go v0.23.2/go.mod h1:T2fsoJDVppxfAPTLd1svUgH1kpPmeXdPESmroSHcL1E=
@@ -859,8 +859,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -929,19 +930,19 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210326220804-49726bf1d181/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
 golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1095,8 +1096,8 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
-google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 h1:TLkBREm4nIsEcexnCjgQd5GQWaHcqMzwQV0TX9pq8S0=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@@ -1164,8 +1165,13 @@ modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g
 modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
 modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
 modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
 modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.24/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/cc/v3 v3.35.25/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/cc/v3 v3.35.26 h1:S4B+fg6/9krLtfZ9lr7pfKiESopiv+Sm6lUUI3oc0fY=
+modernc.org/cc/v3 v3.35.26/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
 modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
 modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
 modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
@@ -1195,11 +1201,30 @@ modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7I
 modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
 modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
 modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
-modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4=
 modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
 modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
-modernc.org/ccgo/v3 v3.12.73 h1:AMk4wEpzWjpODXohKvvnlwLob4Xk8tq3we6CwYh88mA=
 modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
+modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
+modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
+modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
+modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
+modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
+modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
+modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
+modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
+modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
+modernc.org/ccgo/v3 v3.15.14/go.mod h1:144Sz2iBCKogb9OKwsu7hQEub3EVgOlyI8wMUPGKUXQ=
+modernc.org/ccgo/v3 v3.15.15/go.mod h1:z5qltXjU4PJl0pE5nhYQCvA9DhPHiWsl5GWl89+NSYE=
+modernc.org/ccgo/v3 v3.15.16/go.mod h1:XbKRMeMWMdq712Tr5ECgATYMrzJ+g9zAZEj2ktzBe24=
+modernc.org/ccgo/v3 v3.15.17/go.mod h1:bofnFkpRFf5gLY+mBZIyTW6FEcp26xi2lgOFk2Rlvs0=
+modernc.org/ccgo/v3 v3.15.19/go.mod h1:TDJj+DxR26pkDteH2E5WQDj/xlmtsX7JdzkJkaZhOVU=
+modernc.org/ccgo/v3 v3.16.0/go.mod h1:w55kPTAqvRMAYS3Lwij6qhqIuBEYS3Z8QtDkjD8cnik=
+modernc.org/ccgo/v3 v3.16.1/go.mod h1:w55kPTAqvRMAYS3Lwij6qhqIuBEYS3Z8QtDkjD8cnik=
+modernc.org/ccgo/v3 v3.16.2 h1:FUklsEMps3Y2heuTOmn/l6mv83nQgCjW3nsU+1JXzuQ=
+modernc.org/ccgo/v3 v3.16.2/go.mod h1:w55kPTAqvRMAYS3Lwij6qhqIuBEYS3Z8QtDkjD8cnik=
+modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
+modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
 modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
 modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
 modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
@@ -1231,31 +1256,47 @@ modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
 modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
 modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
 modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
-modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
 modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
 modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
-modernc.org/libc v1.11.82 h1:CSl/6n4odvPYWKKqBtFb8e0ZWVTjxDqwxTjaoee9V7E=
 modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
+modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
+modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
+modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
+modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
+modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
+modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
+modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
+modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
+modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
+modernc.org/libc v1.14.6/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
+modernc.org/libc v1.14.7/go.mod h1:f8xfWXW8LW41qb4X5+huVQo5dcfPlq7Cbny2TDheMv0=
+modernc.org/libc v1.14.8/go.mod h1:9+JCLb1MWSY23smyOpIPbd5ED+rSS/ieiDWUpdyO3mo=
+modernc.org/libc v1.14.10/go.mod h1:y1MtIWhwpJFpLYm6grAThtuXJKEsY6xkdZmXbRngIdo=
+modernc.org/libc v1.14.12/go.mod h1:fJdoe23MHu2ruPQkFPPqCpToDi5cckzsbmkI6Ez0LqQ=
+modernc.org/libc v1.15.0 h1:/CTHjQ1QO5mkLDeQICuA9Vh0YvhQTMqtCF2urQTaod8=
+modernc.org/libc v1.15.0/go.mod h1:H1OKCu+NYa9+uQG8WsP7DndMBP61I4PWH8ivWhbdoWQ=
 modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
 modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
-modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
 modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
+modernc.org/memory v1.0.6/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
+modernc.org/memory v1.0.7 h1:UE3cxTRFa5tfUibAV7Jqq8P7zRY0OlJg+yWVIIaluEE=
+modernc.org/memory v1.0.7/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
 modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
 modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
-modernc.org/sqlite v1.14.2-0.20211125151325-d4ed92c0a70f h1:yQwkmqKCIgLzFIfjfPfZAAxLZernckpo7zGTv37Ahv0=
-modernc.org/sqlite v1.14.2-0.20211125151325-d4ed92c0a70f/go.mod h1:YT5XFRKOueohjppHO4cHb54eQlnaUGsZMHoryaCpNo4=
+modernc.org/sqlite v1.17.0 h1:yF5JlxCzQOn2WzyfGAPvHbMNx98ifXLno7a97qggXjE=
+modernc.org/sqlite v1.17.0/go.mod h1:yMNaeEckF88G+PcfRcZRwGE+XnBkzWl/j15bPsDm4QM=
 modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
 modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
-modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw=
-modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
+modernc.org/tcl v1.12.0 h1:Mw2Ukszv5qZbwk/wC9HkDjxhPD4exnd/7/zVxqrB4rY=
+modernc.org/tcl v1.12.0/go.mod h1:9zyAWctRV6IAkMTBeGLyYYqcBrTlVy3ubqiY3dzMfwI=
 modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
 modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
-modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao=
-modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
+modernc.org/z v1.4.0 h1:IpbQb3bOi5Fz17UVGU/mSor8sKIu/7pdCsmGGnQHcxs=
+modernc.org/z v1.4.0/go.mod h1:x6vxerH3hHCPGA3DAM5pERRzuyJEO4UGVfdQC4NZYl0=
 pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g=
 pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go
index a252294ba9..276b761370 100644
--- a/turbo/cli/default_flags.go
+++ b/turbo/cli/default_flags.go
@@ -76,6 +76,7 @@ var DefaultFlags = []cli.Flag{
 	utils.TorrentPortFlag,
 	utils.TorrentMaxPeersFlag,
 	utils.TorrentConnsPerFileFlag,
+	utils.TorrentDownloadSlotsFlag,
 	utils.TorrentUploadRateFlag,
 	utils.TorrentDownloadRateFlag,
 	utils.TorrentVerbosityFlag,
-- 
GitLab