2023-06-15 06:11:51 +00:00
package snapshotsync
import (
"context"
"encoding/binary"
"fmt"
"runtime"
2023-09-29 04:30:02 +00:00
"strings"
2023-06-15 06:11:51 +00:00
"time"
"github.com/ledgerwatch/erigon-lib/chain"
2023-10-18 06:37:39 +00:00
"github.com/ledgerwatch/erigon-lib/chain/snapcfg"
2023-06-15 06:11:51 +00:00
"github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/common/dbg"
"github.com/ledgerwatch/erigon-lib/downloader/downloadergrpc"
"github.com/ledgerwatch/erigon-lib/downloader/snaptype"
proto_downloader "github.com/ledgerwatch/erigon-lib/gointerfaces/downloader"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon-lib/kv/rawdbv3"
"github.com/ledgerwatch/erigon-lib/state"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/turbo/services"
"github.com/ledgerwatch/log/v3"
)
2023-10-28 15:41:50 +00:00
type CaplinMode int
const (
// CaplinModeNone - no caplin mode
NoCaplin CaplinMode = 1
OnlyCaplin CaplinMode = 2
AlsoCaplin CaplinMode = 3
)
2023-06-15 06:11:51 +00:00
func BuildProtoRequest ( downloadRequest [ ] services . DownloadRequest ) * proto_downloader . DownloadRequest {
req := & proto_downloader . DownloadRequest { Items : make ( [ ] * proto_downloader . DownloadItem , 0 , len ( snaptype . AllSnapshotTypes ) ) }
for _ , r := range downloadRequest {
if r . Path != "" {
if r . TorrentHash != "" {
req . Items = append ( req . Items , & proto_downloader . DownloadItem {
TorrentHash : downloadergrpc . String2Proto ( r . TorrentHash ) ,
Path : r . Path ,
} )
} else {
req . Items = append ( req . Items , & proto_downloader . DownloadItem {
Path : r . Path ,
} )
}
} else {
2023-08-18 16:10:35 +00:00
if r . Bor {
for _ , t := range [ ] snaptype . Type { snaptype . BorEvents , snaptype . BorSpans } {
req . Items = append ( req . Items , & proto_downloader . DownloadItem {
Path : snaptype . SegmentFileName ( r . Ranges . From , r . Ranges . To , t ) ,
} )
}
} else {
for _ , t := range snaptype . AllSnapshotTypes {
req . Items = append ( req . Items , & proto_downloader . DownloadItem {
Path : snaptype . SegmentFileName ( r . Ranges . From , r . Ranges . To , t ) ,
} )
}
2023-06-15 06:11:51 +00:00
}
}
}
return req
}
// RequestSnapshotsDownload - builds the snapshots download request and downloads them
func RequestSnapshotsDownload ( ctx context . Context , downloadRequest [ ] services . DownloadRequest , downloader proto_downloader . DownloaderClient ) error {
// start seed large .seg of large size
req := BuildProtoRequest ( downloadRequest )
if _ , err := downloader . Download ( ctx , req ) ; err != nil {
return err
}
return nil
}
// WaitForDownloader - wait for Downloader service to download all expected snapshots
// for MVP we sync with Downloader only once, in future will send new snapshots also
2023-10-28 15:41:50 +00:00
func WaitForDownloader ( logPrefix string , ctx context . Context , histV3 bool , caplin CaplinMode , agg * state . AggregatorV3 , tx kv . RwTx , blockReader services . FullBlockReader , notifier services . DBEventNotifier , cc * chain . Config , snapshotDownloader proto_downloader . DownloaderClient ) error {
2023-06-15 06:11:51 +00:00
snapshots := blockReader . Snapshots ( )
2023-08-18 16:10:35 +00:00
borSnapshots := blockReader . BorSnapshots ( )
2023-06-15 06:11:51 +00:00
if blockReader . FreezingCfg ( ) . NoDownloader {
if err := snapshots . ReopenFolder ( ) ; err != nil {
return err
}
2023-08-18 16:10:35 +00:00
if cc . Bor != nil {
if err := borSnapshots . ReopenFolder ( ) ; err != nil {
return err
}
}
2023-06-15 06:11:51 +00:00
if notifier != nil { // can notify right here, even that write txn is not commit
notifier . OnNewSnapshot ( )
}
return nil
}
// Original intent of snInDB was to contain the file names of the snapshot files for the very first run of the Erigon instance
// Then, we would insist to only download such files, and no others (whitelist)
// However, at some point later, the code was incorrectly changed to update this record in each iteration of the stage loop (function WriteSnapshots)
// And so this list cannot be relied upon as the whitelist, because it also includes all the files created by the node itself
// Not sure what to do it is so far, but the temporary solution is to instead use it as a blacklist (existingFilesMap)
snInDB , snHistInDB , err := rawdb . ReadSnapshots ( tx )
if err != nil {
return err
}
dbEmpty := len ( snInDB ) == 0
2023-08-18 16:10:35 +00:00
var existingFilesMap , borExistingFilesMap map [ string ] struct { }
var missingSnapshots , borMissingSnapshots [ ] * services . Range
2023-06-15 06:11:51 +00:00
if ! dbEmpty {
existingFilesMap , missingSnapshots , err = snapshots . ScanDir ( )
if err != nil {
return err
}
2023-08-18 16:10:35 +00:00
if cc . Bor == nil {
borExistingFilesMap = map [ string ] struct { } { }
} else {
borExistingFilesMap , borMissingSnapshots , err = borSnapshots . ScanDir ( )
if err != nil {
return err
}
}
2023-06-15 06:11:51 +00:00
}
if len ( missingSnapshots ) > 0 {
log . Warn ( fmt . Sprintf ( "[%s] downloading missing snapshots" , logPrefix ) )
}
// send all hashes to the Downloader service
2023-08-18 16:10:35 +00:00
preverifiedBlockSnapshots := snapcfg . KnownCfg ( cc . ChainName , [ ] string { } /* whitelist */ , snHistInDB ) . Preverified
2023-06-15 06:11:51 +00:00
downloadRequest := make ( [ ] services . DownloadRequest , 0 , len ( preverifiedBlockSnapshots ) + len ( missingSnapshots ) )
// build all download requests
// builds preverified snapshots request
for _ , p := range preverifiedBlockSnapshots {
2023-09-29 04:30:02 +00:00
if ! histV3 {
if strings . HasPrefix ( p . Name , "domain" ) || strings . HasPrefix ( p . Name , "history" ) || strings . HasPrefix ( p . Name , "idx" ) {
continue
}
}
2023-10-28 15:41:50 +00:00
if caplin == NoCaplin && strings . Contains ( p . Name , "beaconblocks" ) {
continue
}
if caplin == OnlyCaplin && ! strings . Contains ( p . Name , "beaconblocks" ) {
continue
}
2023-09-29 04:30:02 +00:00
2023-08-18 16:10:35 +00:00
_ , exists := existingFilesMap [ p . Name ]
_ , borExists := borExistingFilesMap [ p . Name ]
if ! exists && ! borExists { // Not to download existing files "behind the scenes"
downloadRequest = append ( downloadRequest , services . NewDownloadRequest ( nil , p . Name , p . Hash , false /* Bor */ ) )
2023-06-15 06:11:51 +00:00
}
}
// builds missing snapshots request
for _ , r := range missingSnapshots {
2023-08-18 16:10:35 +00:00
downloadRequest = append ( downloadRequest , services . NewDownloadRequest ( r , "" , "" , false /* Bor */ ) )
}
if cc . Bor != nil {
for _ , r := range borMissingSnapshots {
downloadRequest = append ( downloadRequest , services . NewDownloadRequest ( r , "" , "" , true /* Bor */ ) )
}
2023-06-15 06:11:51 +00:00
}
log . Info ( fmt . Sprintf ( "[%s] Fetching torrent files metadata" , logPrefix ) )
for {
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
default :
}
if err := RequestSnapshotsDownload ( ctx , downloadRequest , snapshotDownloader ) ; err != nil {
log . Error ( fmt . Sprintf ( "[%s] call downloader" , logPrefix ) , "err" , err )
time . Sleep ( 10 * time . Second )
continue
}
break
}
downloadStartTime := time . Now ( )
const logInterval = 20 * time . Second
logEvery := time . NewTicker ( logInterval )
defer logEvery . Stop ( )
var m runtime . MemStats
// Check once without delay, for faster erigon re-start
stats , err := snapshotDownloader . Stats ( ctx , & proto_downloader . StatsRequest { } )
if err == nil && stats . Completed {
goto Finish
}
// Print download progress until all segments are available
Loop :
for {
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
case <- logEvery . C :
if stats , err := snapshotDownloader . Stats ( ctx , & proto_downloader . StatsRequest { } ) ; err != nil {
log . Warn ( "Error while waiting for snapshots progress" , "err" , err )
} else if stats . Completed {
2023-08-18 16:10:35 +00:00
/ *
if ! blockReader . FreezingCfg ( ) . Verify { // will verify after loop
if _ , err := snapshotDownloader . Verify ( ctx , & proto_downloader . VerifyRequest { } ) ; err != nil {
return err
}
2023-06-15 06:11:51 +00:00
}
2023-08-18 16:10:35 +00:00
* /
2023-06-15 06:11:51 +00:00
log . Info ( fmt . Sprintf ( "[%s] download finished" , logPrefix ) , "time" , time . Since ( downloadStartTime ) . String ( ) )
break Loop
} else {
if stats . MetadataReady < stats . FilesTotal {
log . Info ( fmt . Sprintf ( "[%s] Waiting for torrents metadata: %d/%d" , logPrefix , stats . MetadataReady , stats . FilesTotal ) )
2023-09-13 04:49:45 +00:00
continue
2023-06-15 06:11:51 +00:00
}
dbg . ReadMemStats ( & m )
downloadTimeLeft := calculateTime ( stats . BytesTotal - stats . BytesCompleted , stats . DownloadRate )
2023-10-04 05:34:45 +00:00
suffix := "downloading"
2023-09-13 06:42:24 +00:00
if stats . Progress > 0 && stats . DownloadRate == 0 {
2023-10-04 05:34:45 +00:00
suffix += " (or verifying)"
2023-09-13 06:42:24 +00:00
}
log . Info ( fmt . Sprintf ( "[%s] %s" , logPrefix , suffix ) ,
2023-06-15 06:11:51 +00:00
"progress" , fmt . Sprintf ( "%.2f%% %s/%s" , stats . Progress , common . ByteCount ( stats . BytesCompleted ) , common . ByteCount ( stats . BytesTotal ) ) ,
2023-09-13 06:42:24 +00:00
"time-left" , downloadTimeLeft ,
"total-time" , time . Since ( downloadStartTime ) . Round ( time . Second ) . String ( ) ,
2023-06-15 06:11:51 +00:00
"download" , common . ByteCount ( stats . DownloadRate ) + "/s" ,
"upload" , common . ByteCount ( stats . UploadRate ) + "/s" ,
"peers" , stats . PeersUnique ,
"files" , stats . FilesTotal ,
2023-09-13 06:42:24 +00:00
"connections" , stats . ConnectionsTotal ,
2023-06-15 06:11:51 +00:00
"alloc" , common . ByteCount ( m . Alloc ) , "sys" , common . ByteCount ( m . Sys ) ,
)
}
}
}
Finish :
if blockReader . FreezingCfg ( ) . Verify {
if _ , err := snapshotDownloader . Verify ( ctx , & proto_downloader . VerifyRequest { } ) ; err != nil {
return err
}
}
2023-08-18 16:10:35 +00:00
stats , err = snapshotDownloader . Stats ( ctx , & proto_downloader . StatsRequest { } )
if err != nil {
return err
}
if ! stats . Completed {
goto Loop
}
2023-06-15 06:11:51 +00:00
if err := snapshots . ReopenFolder ( ) ; err != nil {
return err
}
2023-08-18 16:10:35 +00:00
if cc . Bor != nil {
if err := borSnapshots . ReopenFolder ( ) ; err != nil {
return err
}
}
2023-06-15 06:11:51 +00:00
if err := agg . OpenFolder ( ) ; err != nil {
return err
}
if err := rawdb . WriteSnapshots ( tx , blockReader . FrozenFiles ( ) , agg . Files ( ) ) ; err != nil {
return err
}
if notifier != nil { // can notify right here, even that write txn is not commit
notifier . OnNewSnapshot ( )
}
firstNonGenesis , err := rawdbv3 . SecondKey ( tx , kv . Headers )
if err != nil {
return err
}
if firstNonGenesis != nil {
firstNonGenesisBlockNumber := binary . BigEndian . Uint64 ( firstNonGenesis )
if snapshots . SegmentsMax ( ) + 1 < firstNonGenesisBlockNumber {
log . Warn ( fmt . Sprintf ( "[%s] Some blocks are not in snapshots and not in db" , logPrefix ) , "max_in_snapshots" , snapshots . SegmentsMax ( ) , "min_in_db" , firstNonGenesisBlockNumber )
}
}
return nil
}
func calculateTime ( amountLeft , rate uint64 ) string {
if rate == 0 {
return "999hrs:99m"
}
timeLeftInSeconds := amountLeft / rate
hours := timeLeftInSeconds / 3600
minutes := ( timeLeftInSeconds / 60 ) % 60
return fmt . Sprintf ( "%dhrs:%dm" , hours , minutes )
}