cmd/utils: don't store entire era file in-memory during import / export

This commit is contained in:
lightclient@protonmail.com 2023-05-22 16:59:54 +02:00 committed by lightclient
parent d58c9137a3
commit 76e08e0cf5
No known key found for this signature in database
GPG key ID: 75C916AFEE20183E

View file

@ -19,7 +19,6 @@ package utils
import ( import (
"bufio" "bufio"
"bytes"
"compress/gzip" "compress/gzip"
"crypto/sha256" "crypto/sha256"
"errors" "errors"
@ -263,22 +262,29 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
reported = time.Now() reported = time.Now()
imported = 0 imported = 0
forker = core.NewForkChoice(chain, nil) forker = core.NewForkChoice(chain, nil)
h = sha256.New()
buf []byte
) )
for i, filename := range entries { for i, filename := range entries {
// Read entire Era1 to memory. Max historical Era1 is around err := func() error {
// 600MB. This is a lot to load at once, but it speeds up the f, err := os.Open(path.Join(dir, filename))
// import substantially.
f, err := os.ReadFile(path.Join(dir, filename))
if err != nil { if err != nil {
return fmt.Errorf("unable to open era: %w", err) return fmt.Errorf("unable to open era: %w", err)
} }
defer f.Close()
if have, want := common.Hash(sha256.Sum256(f)).Hex(), checksums[i]; have != want { // Validate checksum.
if _, err := io.Copy(h, f); err != nil {
return fmt.Errorf("unable to recalculate checksum: %w", err)
}
if have, want := common.BytesToHash(h.Sum(buf)).Hex(), checksums[i]; have != want {
return fmt.Errorf("checksum mismatch: have %s, want %s", have, want) return fmt.Errorf("checksum mismatch: have %s, want %s", have, want)
} }
h.Reset()
buf = buf[:0]
// Import all block data from Era1. // Import all block data from Era1.
r, err := era.NewReader(bytes.NewReader(f)) r, err := era.NewReader(f)
if err != nil { if err != nil {
return fmt.Errorf("error making era reader: %w", err) return fmt.Errorf("error making era reader: %w", err)
} }
@ -309,6 +315,11 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
reported = time.Now() reported = time.Now()
} }
} }
return nil
}()
if err != nil {
return err
}
} }
return nil return nil
@ -401,13 +412,20 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) er
var ( var (
start = time.Now() start = time.Now()
reported = time.Now() reported = time.Now()
h = sha256.New()
buf []byte
checksums []string checksums []string
) )
for i := first; i <= last; i += step { for i := first; i <= last; i += step {
var ( err := func() error {
buf = bytes.NewBuffer(nil) filename := path.Join(dir, era.Filename(network, int(i/step), common.Hash{}))
w = era.NewBuilder(buf) f, err := os.Create(filename)
) if err != nil {
return fmt.Errorf("could not create era file: %w", err)
}
defer f.Close()
w := era.NewBuilder(f)
for j := uint64(0); j < step && j <= last-i; j++ { for j := uint64(0); j < step && j <= last-i; j++ {
var ( var (
n = i + j n = i + j
@ -432,16 +450,24 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) er
if err != nil { if err != nil {
return fmt.Errorf("export failed to finalize %d: %w", step/i, err) return fmt.Errorf("export failed to finalize %d: %w", step/i, err)
} }
// Set correct filename with root.
os.Rename(filename, path.Join(dir, era.Filename(network, int(i/step), root)))
// Compute checksum of entire Era1. // Compute checksum of entire Era1.
checksums = append(checksums, common.Hash(sha256.Sum256(buf.Bytes())).Hex()) if _, err := f.Seek(0, io.SeekStart); err != nil {
return err
// Write Era1 to disk. }
filename := path.Join(dir, era.Filename(network, int(i/step), root)) if _, err := io.Copy(h, f); err != nil {
if err := os.WriteFile(filename, buf.Bytes(), os.ModePerm); err != nil { return fmt.Errorf("unable to calculate checksum: %w", err)
}
checksums = append(checksums, common.BytesToHash(h.Sum(buf)).Hex())
h.Reset()
buf = buf[:0]
return nil
}()
if err != nil {
return err return err
} }
if time.Since(reported) >= 8*time.Second { if time.Since(reported) >= 8*time.Second {
log.Info("Exporting blocks", "exported", i, "elapsed", common.PrettyDuration(time.Since(start))) log.Info("Exporting blocks", "exported", i, "elapsed", common.PrettyDuration(time.Since(start)))
reported = time.Now() reported = time.Now()