internal/era,cmd/utils,cmd/era: change to iterator interface for reading era entries

This commit is contained in:
lightclient@protonmail.com 2023-06-02 17:03:02 +02:00 committed by lightclient
parent 008ed82224
commit 3fa17e775f
No known key found for this signature in database
GPG key ID: 75C916AFEE20183E
6 changed files with 382 additions and 259 deletions

View file

@ -19,7 +19,6 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"math/big" "math/big"
"os" "os"
"path" "path"
@ -111,19 +110,15 @@ func block(ctx *cli.Context) error {
if err != nil { if err != nil {
return fmt.Errorf("invalid block number: %w", err) return fmt.Errorf("invalid block number: %w", err)
} }
f, err := open(ctx, num/uint64(ctx.Int(eraSizeFlag.Name))) e, err := open(ctx, num/uint64(ctx.Int(eraSizeFlag.Name)))
if err != nil { if err != nil {
return fmt.Errorf("error opening era: %w", err) return fmt.Errorf("error opening era: %w", err)
} }
defer f.Close() defer e.Close()
r, err := era.NewReader(f)
if err != nil {
return fmt.Errorf("error making era reader: %w", err)
}
// Read block with number. // Read block with number.
block, err := r.ReadBlock(num) block, err := e.GetBlockByNumber(num)
if err != nil { if err != nil {
return fmt.Errorf("error reading era: %w", err) return fmt.Errorf("error reading block %d: %w", num, err)
} }
// Convert block to JSON and print. // Convert block to JSON and print.
val, err := ethapi.RPCMarshalBlock(block, ctx.Bool(txsFlag.Name), ctx.Bool(txsFlag.Name), params.MainnetChainConfig) val, err := ethapi.RPCMarshalBlock(block, ctx.Bool(txsFlag.Name), ctx.Bool(txsFlag.Name), params.MainnetChainConfig)
@ -144,20 +139,16 @@ func info(ctx *cli.Context) error {
if err != nil { if err != nil {
return fmt.Errorf("invalid epoch number: %w", err) return fmt.Errorf("invalid epoch number: %w", err)
} }
f, err := open(ctx, epoch) e, err := open(ctx, epoch)
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer e.Close()
r, err := era.NewReader(f) acc, err := e.Accumulator()
if err != nil {
return fmt.Errorf("error creating era reader: %w", err)
}
acc, err := r.Accumulator()
if err != nil { if err != nil {
return fmt.Errorf("error reading accumulator: %w", err) return fmt.Errorf("error reading accumulator: %w", err)
} }
td, err := r.InitialTD() td, err := e.InitialTD()
if err != nil { if err != nil {
return fmt.Errorf("error reading total difficulty: %w", err) return fmt.Errorf("error reading total difficulty: %w", err)
} }
@ -167,7 +158,7 @@ func info(ctx *cli.Context) error {
StartBlock uint64 `json:"startBlock"` StartBlock uint64 `json:"startBlock"`
Count uint64 `json:"count"` Count uint64 `json:"count"`
}{ }{
acc, td, r.Start(), r.Count(), acc, td, e.Start(), e.Count(),
} }
b, _ := json.MarshalIndent(info, "", " ") b, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(b)) fmt.Println(string(b))
@ -175,7 +166,7 @@ func info(ctx *cli.Context) error {
} }
// open opens an era1 file at a certain epoch. // open opens an era1 file at a certain epoch.
func open(ctx *cli.Context, epoch uint64) (*os.File, error) { func open(ctx *cli.Context, epoch uint64) (*era.Era, error) {
var ( var (
dir = ctx.String(dirFlag.Name) dir = ctx.String(dirFlag.Name)
network = ctx.String(networkFlag.Name) network = ctx.String(networkFlag.Name)
@ -187,7 +178,7 @@ func open(ctx *cli.Context, epoch uint64) (*os.File, error) {
if epoch >= uint64(len(entries)) { if epoch >= uint64(len(entries)) {
return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch) return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch)
} }
return os.Open(path.Join(dir, entries[epoch])) return era.Open(path.Join(dir, entries[epoch]))
} }
// verify checks each era1 file in a directory to ensure it is well-formed and // verify checks each era1 file in a directory to ensure it is well-formed and
@ -223,29 +214,21 @@ func verify(ctx *cli.Context) error {
// Wrap in function so defers don't stack. // Wrap in function so defers don't stack.
err := func() error { err := func() error {
name := entries[i] name := entries[i]
f, err := os.Open(path.Join(dir, name)) e, err := era.Open(path.Join(dir, name))
if err != nil { if err != nil {
return fmt.Errorf("error opening era1 file %s: %w", name, err) return fmt.Errorf("error opening era1 file %s: %w", name, err)
} }
defer f.Close() defer e.Close()
r, err := era.NewReader(f)
if err != nil {
return fmt.Errorf("unable to make era reader: %w", err)
}
// Read accumulator and check against expected. // Read accumulator and check against expected.
if got, err := r.Accumulator(); err != nil { if got, err := e.Accumulator(); err != nil {
return fmt.Errorf("error retrieving accumulator for %s: %w", name, err) return fmt.Errorf("error retrieving accumulator for %s: %w", name, err)
} else if got != want { } else if got != want {
return fmt.Errorf("invalid root %s: got %s, want %s", name, got, want) return fmt.Errorf("invalid root %s: got %s, want %s", name, got, want)
} }
// Recompute accumulator. // Recompute accumulator.
if err := checkAccumulator(r); err != nil { if err := checkAccumulator(e); err != nil {
return fmt.Errorf("error verify era1 file %s: %w", name, err) return fmt.Errorf("error verify era1 file %s: %w", name, err)
} }
// Give the user some feedback that something is happening. // Give the user some feedback that something is happening.
if time.Since(reported) >= 8*time.Second { if time.Since(reported) >= 8*time.Second {
fmt.Printf("Verifying Era1 files \t\t verified=%d,\t elapsed=%s\n", i, common.PrettyDuration(time.Since(start))) fmt.Printf("Verifying Era1 files \t\t verified=%d,\t elapsed=%s\n", i, common.PrettyDuration(time.Since(start)))
@ -262,21 +245,25 @@ func verify(ctx *cli.Context) error {
} }
// checkAccumulator verifies the accumulator matches the data in the Era. // checkAccumulator verifies the accumulator matches the data in the Era.
func checkAccumulator(r *era.Reader) error { func checkAccumulator(e *era.Era) error {
var ( var (
err error err error
start = r.Start() start = e.Start()
want common.Hash want common.Hash
td *big.Int td *big.Int
tds = make([]*big.Int, 0) tds = make([]*big.Int, 0)
hashes = make([]common.Hash, 0) hashes = make([]common.Hash, 0)
) )
if want, err = r.Accumulator(); err != nil { if want, err = e.Accumulator(); err != nil {
return fmt.Errorf("error reading accumulator: %w", err) return fmt.Errorf("error reading accumulator: %w", err)
} }
if td, err = r.InitialTD(); err != nil { if td, err = e.InitialTD(); err != nil {
return fmt.Errorf("error reading total difficulty: %w", err) return fmt.Errorf("error reading total difficulty: %w", err)
} }
it, err := era.NewIterator(e)
if err != nil {
return fmt.Errorf("error making era iterator: %w", err)
}
// Starting at epoch 0, iterate through all available era1 files and // Starting at epoch 0, iterate through all available era1 files and
// check the following: // check the following:
// * the block index is constructed correctly // * the block index is constructed correctly
@ -284,13 +271,14 @@ func checkAccumulator(r *era.Reader) error {
// * the accumulator is correct by recomputing it locally, // * the accumulator is correct by recomputing it locally,
// which verifies the blocks are all correct (via hash) // which verifies the blocks are all correct (via hash)
// * the receipts root matches the value in the block // * the receipts root matches the value in the block
for j := 0; ; j++ { for j := 0; it.Next(); j++ {
// read() walks the block index, so we're able to // next() walks the block index, so we're able to
// implicitly verify it. // implicitly verify it.
block, receipts, err := r.Read() if it.Error() != nil {
if err == io.EOF { return fmt.Errorf("error reading block %d: %w", start+uint64(j), err)
break }
} else if err != nil { block, receipts, err := it.BlockAndReceipts()
if it.Error() != nil {
return fmt.Errorf("error reading block %d: %w", start+uint64(j), err) return fmt.Errorf("error reading block %d: %w", start+uint64(j), err)
} }
tr := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)) tr := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil))

View file

@ -285,20 +285,27 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
buf.Reset() buf.Reset()
// Import all block data from Era1. // Import all block data from Era1.
r, err := era.NewReader(f) e, err := era.From(f)
if err != nil {
return fmt.Errorf("error opening era: %w", err)
}
it, err := era.NewIterator(e)
if err != nil { if err != nil {
return fmt.Errorf("error making era reader: %w", err) return fmt.Errorf("error making era reader: %w", err)
} }
for j := 0; ; j += 1 { for j := 0; it.Next(); j++ {
n := i*era.MaxEra1Size + j n := i*era.MaxEra1Size + j
block, receipts, err := r.Read() block, err := it.Block()
if err == io.EOF { if err != nil {
break
} else if err != nil {
return fmt.Errorf("error reading block %d: %w", n, err) return fmt.Errorf("error reading block %d: %w", n, err)
} else if block.Number().BitLen() == 0 { }
if block.Number().BitLen() == 0 {
continue // skip genesis continue // skip genesis
} }
receipts, err := it.Receipts()
if err != nil {
return fmt.Errorf("error reading receipts %d: %w", n, err)
}
if status, err := chain.HeaderChain().InsertHeaderChain([]*types.Header{block.Header()}, start, forker); err != nil { if status, err := chain.HeaderChain().InsertHeaderChain([]*types.Header{block.Header()}, start, forker); err != nil {
return fmt.Errorf("error inserting header %d: %w", n, err) return fmt.Errorf("error inserting header %d: %w", n, err)
} else if status != core.CanonStatTy { } else if status != core.CanonStatTy {

View file

@ -107,42 +107,54 @@ func TestHistoryImportAndExport(t *testing.T) {
// Verify each Era. // Verify each Era.
entries, _ := era.ReadDir(dir, "mainnet") entries, _ := era.ReadDir(dir, "mainnet")
for i, filename := range entries { for i, filename := range entries {
f, err := os.ReadFile(path.Join(dir, filename)) f, err := os.Open(path.Join(dir, filename))
if err != nil { if err != nil {
t.Fatalf("error opening era file: %v", err) t.Fatalf("error opening era file: %v", err)
} }
if want, got := common.HexToHash(checksums[i]), common.Hash(sha256.Sum256(f)); want != got {
var (
h = sha256.New()
buf = bytes.NewBuffer(nil)
)
if _, err := io.Copy(h, f); err != nil {
t.Fatalf("unable to recalculate checksum: %v", err)
}
if got, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; got != want {
t.Fatalf("checksum %d does not match: got %s, want %s", i, got, want) t.Fatalf("checksum %d does not match: got %s, want %s", i, got, want)
} }
r, err := era.NewReader(bytes.NewReader(f))
e, err := era.From(f)
if err != nil {
t.Fatalf("error opening era: %v", err)
}
it, err := era.NewIterator(e)
if err != nil { if err != nil {
t.Fatalf("error making era reader: %v", err) t.Fatalf("error making era reader: %v", err)
} }
for j := 0; ; j += 1 { for j := 0; it.Next(); j++ {
block, receipts, err := r.Read() n := i*int(step) + j
if err == io.EOF { if it.Error() != nil {
break t.Fatalf("error reading block entry %d: %v", n, err)
} else if err != nil {
t.Fatalf("error reading era file %d: %v", i, err)
} }
var ( block, receipts, err := it.BlockAndReceipts()
n = i*int(step) + j if err != nil {
want = chain.GetBlockByNumber(uint64(n)) t.Fatalf("error reading block entry %d: %v", n, err)
) }
want := chain.GetBlockByNumber(uint64(n))
if want, got := uint64(n), block.NumberU64(); want != got { if want, got := uint64(n), block.NumberU64(); want != got {
t.Fatalf("blocks out of order: want %d, got %d", want, got) t.Fatalf("blocks out of order: want %d, got %d", want, got)
} }
if want.Hash() != block.Hash() { if want.Hash() != block.Hash() {
t.Fatalf("block hash mismatch %d: want %s, got %s", i+j, want.Hash().Hex(), block.Hash().Hex()) t.Fatalf("block hash mismatch %d: want %s, got %s", n, want.Hash().Hex(), block.Hash().Hex())
} }
if got := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); got != want.TxHash() { if got := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); got != want.TxHash() {
t.Fatalf("tx hash %d mismatch: want %s, got %s", i+j, want.TxHash(), got) t.Fatalf("tx hash %d mismatch: want %s, got %s", n, want.TxHash(), got)
} }
if got := types.CalcUncleHash(block.Uncles()); got != want.UncleHash() { if got := types.CalcUncleHash(block.Uncles()); got != want.UncleHash() {
t.Fatalf("uncle hash %d mismatch: want %s, got %s", i+j, want.UncleHash(), got) t.Fatalf("uncle hash %d mismatch: want %s, got %s", n, want.UncleHash(), got)
} }
if got := types.DeriveSha(receipts, trie.NewStackTrie(nil)); got != want.ReceiptHash() { if got := types.DeriveSha(receipts, trie.NewStackTrie(nil)); got != want.ReceiptHash() {
t.Fatalf("receipt root %d mismatch: want %s, got %s", i+j, want.ReceiptHash(), got) t.Fatalf("receipt root %d mismatch: want %s, got %s", n, want.ReceiptHash(), got)
} }
} }
} }

View file

@ -26,6 +26,7 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -83,197 +84,75 @@ func ReadDir(dir, network string) ([]string, error) {
return eras, nil return eras, nil
} }
// Reader reads an Era1 archive. type ReadAtSeekCloser interface {
// See Builder documentation for a detailed explanation of the Era1 format.
type Reader struct {
r io.ReaderAt
e *e2store.Reader
buf [8]byte // buffer reading entry offsets
next uint64 // next block to read
length int64 // total length of r
metadata metadata // start, count info
}
type ReadAtSeeker interface {
io.ReaderAt io.ReaderAt
io.Seeker io.Seeker
io.Closer
} }
// NewReader returns a new Reader instance. // Era reads and Era1 file.
func NewReader(r ReadAtSeeker) (*Reader, error) { type Era struct {
length, err := r.Seek(0, io.SeekEnd) f ReadAtSeekCloser // backing era1 file
s *e2store.Reader // e2store reader over f
m metadata // start, count, length info
mu *sync.Mutex // lock for buf
buf [8]byte // buffer reading entry offsets
}
// From returns an Era backed by f.
func From(f ReadAtSeekCloser) (*Era, error) {
m, err := readMetadata(f)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m, err := readMetadata(r, length) return &Era{
if err != nil { f: f,
return nil, err s: e2store.NewReader(f),
} m: m,
return &Reader{ mu: new(sync.Mutex),
r: r,
e: e2store.NewReader(r),
next: m.start,
length: length,
metadata: m,
}, nil }, nil
} }
// readOffset reads a specific block's offset from the block index. The value n // Open returns an Era backed by the given filename.
// is the absolute block number desired. func Open(filename string) (*Era, error) {
func (r *Reader) readOffset(n uint64) (int64, error) { f, err := os.Open(filename)
var (
firstIndex = -8 - int64(r.metadata.count)*8 // size of count - index entries
indexOffset = int64(n-r.metadata.start) * 8 // desired index * size of indexes
offOffset = r.length + firstIndex + indexOffset // offset of block offset
)
r.clearBuffer()
if _, err := r.r.ReadAt(r.buf[:], offOffset); err != nil {
return 0, err
}
// Since the block offset is relative from its location + size of index
// value (8), we need to add it to it's offset to get the block's
// absolute offset.
return offOffset + 8 + int64(binary.LittleEndian.Uint64(r.buf[:])), nil
}
// Read reads one (block, receipts) tuple from an Era1 archive.
func (r *Reader) Read() (*types.Block, types.Receipts, error) {
block, receipts, err := r.ReadBlockAndReceipts(r.next)
if err != nil {
return nil, nil, err
}
r.next += 1
return block, receipts, nil
}
// readBlob reads an entry of data.
func (r *Reader) readEntry(n uint64, skip int) (*e2store.Entry, error) {
if n < r.metadata.start || r.metadata.start+r.metadata.count < n {
return nil, fmt.Errorf("request out-of-bounds: want %d, start: %d, count: %d", n, r.metadata.start, r.metadata.count)
}
// Read the specified block's offset from the block index.
off, err := r.readOffset(n)
if err != nil {
return nil, fmt.Errorf("error reading block offset: %w", err)
}
// Skip to the requested entry.
for i := 0; i < skip; i++ {
if length, err := r.e.LengthAt(off); err != nil {
return nil, err
} else {
off += length
}
}
// Read entry.
var entry e2store.Entry
if _, err := r.e.ReadAt(&entry, off); err != nil {
return nil, err
}
return &entry, nil
}
// readHeaderRLP reads the header number n RLP.
func (r *Reader) readHeaderRLP(n uint64) ([]byte, error) {
e, err := r.readEntry(n, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e.Type != TypeCompressedHeader { return From(f)
return nil, fmt.Errorf("expected header entry, got %x", e.Type)
}
return io.ReadAll(snappy.NewReader(bytes.NewReader(e.Value)))
} }
// readBodyRLP reads the block body number n RLP. func (e *Era) Close() error {
func (r *Reader) readBodyRLP(n uint64) ([]byte, error) { return e.f.Close()
e, err := r.readEntry(n, 1) }
func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
return nil, fmt.Errorf("out-of-bounds")
}
off, err := e.readOffset(num)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e.Type != TypeCompressedBody { r, n, err := newSnappyReader(e.s, off)
return nil, fmt.Errorf("expected body entry, got %x", e.Type)
}
return io.ReadAll(snappy.NewReader(bytes.NewReader(e.Value)))
}
// readReceiptsRLP reads the receipts RLP associated with number n.
func (r *Reader) readReceiptsRLP(n uint64) ([]byte, error) {
e, err := r.readEntry(n, 2)
if err != nil {
return nil, err
}
if e.Type != TypeCompressedReceipts {
return nil, fmt.Errorf("expected receipts entry, got %x", e.Type)
}
return io.ReadAll(snappy.NewReader(bytes.NewReader(e.Value)))
}
// readTotalDifficulty reads the total difficulty of block number n.
func (r *Reader) readTotalDifficulty(n uint64) (*big.Int, error) {
e, err := r.readEntry(n, 3)
if err != nil {
return nil, err
}
if e.Type != TypeTotalDifficulty {
return nil, fmt.Errorf("expected TD entry, got %x", e.Type)
}
return new(big.Int).SetBytes(reverseOrder(e.Value)), nil
}
// ReadHeader reads the header number n.
func (r *Reader) ReadHeader(n uint64) (*types.Header, error) {
h, err := r.readHeaderRLP(n)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var header types.Header var header types.Header
if err := rlp.DecodeBytes(h, &header); err != nil { if err := rlp.Decode(r, &header); err != nil {
return nil, err
}
return &header, nil
}
// ReadBlock reads the block number n.
func (r *Reader) ReadBlock(n uint64) (*types.Block, error) {
header, err := r.ReadHeader(n)
if err != nil {
return nil, err
}
b, err := r.readBodyRLP(n)
if err != nil {
return nil, err return nil, err
} }
off += int64(n)
var body types.Body var body types.Body
if err := rlp.DecodeBytes(b, &body); err != nil { if err := rlp.Decode(r, &body); err != nil {
return nil, err return nil, err
} }
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles), nil return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil
}
// ReadBlockAndReceipts reads the block number n and associated receipts.
func (r *Reader) ReadBlockAndReceipts(n uint64) (*types.Block, types.Receipts, error) {
// Read block.
block, err := r.ReadBlock(n)
if err != nil {
return nil, nil, err
}
// Read receipts.
rr, err := r.readReceiptsRLP(n)
if err != nil {
return nil, nil, err
}
// Decode receipts.
var receipts types.Receipts
if err := rlp.DecodeBytes(rr, &receipts); err != nil {
return nil, nil, err
}
return block, receipts, err
} }
// Accumulator reads the accumulator entry in the Era1 file. // Accumulator reads the accumulator entry in the Era1 file.
func (r *Reader) Accumulator() (common.Hash, error) { func (e *Era) Accumulator() (common.Hash, error) {
entry, err := r.e.Find(TypeAccumulator) entry, err := e.s.Find(TypeAccumulator)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -282,53 +161,126 @@ func (r *Reader) Accumulator() (common.Hash, error) {
// InitialTD returns initial total difficulty before the difficulty of the // InitialTD returns initial total difficulty before the difficulty of the
// first block of the Era1 is applied. // first block of the Era1 is applied.
func (r *Reader) InitialTD() (*big.Int, error) { func (e *Era) InitialTD() (*big.Int, error) {
h, err := r.ReadHeader(r.Start()) var (
if err != nil { r io.Reader
header types.Header
rawTd []byte
n int
off int64
err error
)
// Read first header.
if off, err = e.readOffset(e.m.start); err != nil {
return nil, err return nil, err
} }
// Above seek also sets reader so next TD entry will be for this block. if r, n, err = newSnappyReader(e.s, off); err != nil {
entry, err := r.e.Find(TypeTotalDifficulty)
if err != nil {
return nil, err return nil, err
} }
td := new(big.Int).SetBytes(reverseOrder(entry.Value)) if err := rlp.Decode(r, header); err != nil {
return td.Sub(td, h.Difficulty), nil return nil, err
}
off += int64(n)
// Skip over next two records.
for i := 0; i < 2; i++ {
length, err := e.s.LengthAt(off)
if err != nil {
return nil, err
}
off += length
}
// Read total difficulty after first block.
if r, n, err = newReader(e.s, off); err != nil {
return nil, err
}
if err := rlp.Decode(r, rawTd); err != nil {
return nil, err
}
td := new(big.Int).SetBytes(reverseOrder(rawTd))
return td.Sub(td, header.Difficulty), nil
} }
// Start returns the listed start block. // Start returns the listed start block.
func (r *Reader) Start() uint64 { func (e *Era) Start() uint64 {
return r.metadata.start return e.m.start
} }
// Count returns the total number of blocks in the Era1. // Count returns the total number of blocks in the Era1.
func (r *Reader) Count() uint64 { func (e *Era) Count() uint64 {
return r.metadata.count return e.m.count
}
// readOffset reads a specific block's offset from the block index. The value n
// is the absolute block number desired.
func (e *Era) readOffset(n uint64) (int64, error) {
var (
firstIndex = -8 - int64(e.m.count)*8 // size of count - index entries
indexOffset = int64(n-e.m.start) * 8 // desired index * size of indexes
offOffset = e.m.length + firstIndex + indexOffset // offset of block offset
)
e.mu.Lock()
defer e.mu.Unlock()
clearBuffer(e.buf[:])
if _, err := e.f.ReadAt(e.buf[:], offOffset); err != nil {
return 0, err
}
// Since the block offset is relative from its location + size of index
// value (8), we need to add it to it's offset to get the block's
// absolute offset.
return offOffset + 8 + int64(binary.LittleEndian.Uint64(e.buf[:])), nil
}
// newReader returns an io.Reader for the e2store entry value at off.
func newReader(e *e2store.Reader, off int64) (io.Reader, int, error) {
var (
entry e2store.Entry
n int
err error
)
if n, err = e.ReadAt(&entry, off); err != nil {
return nil, n, err
}
return bytes.NewReader(entry.Value), n, nil
}
// newReader returns a snappy.Reader for the e2store entry value at off.
func newSnappyReader(e *e2store.Reader, off int64) (io.Reader, int, error) {
r, n, err := newReader(e, off)
return snappy.NewReader(r), n, err
} }
// clearBuffer zeroes out the buffer. // clearBuffer zeroes out the buffer.
func (r *Reader) clearBuffer() { func clearBuffer(buf []byte) {
for i := 0; i < len(r.buf); i++ { for i := 0; i < len(buf); i++ {
r.buf[i] = 0 buf[i] = 0
} }
} }
// metadata wraps the metadata in the block index. // metadata wraps the metadata in the block index.
type metadata struct { type metadata struct {
start, count uint64 start uint64
count uint64
length int64
} }
// readMetadata reads the metadata stored in an Era1 file's block index. // readMetadata reads the metadata stored in an Era1 file's block index.
func readMetadata(r io.ReaderAt, length int64) (m metadata, err error) { func readMetadata(f ReadAtSeekCloser) (m metadata, err error) {
// Determine length of reader.
if m.length, err = f.Seek(0, io.SeekEnd); err != nil {
return
}
b := make([]byte, 16) b := make([]byte, 16)
// Read count. It's the last 8 bytes of the file. // Read count. It's the last 8 bytes of the file.
if _, err = r.ReadAt(b[:8], length-8); err != nil { if _, err = f.ReadAt(b[:8], m.length-8); err != nil {
return return
} }
m.count = binary.LittleEndian.Uint64(b) m.count = binary.LittleEndian.Uint64(b)
// Read start. It's at the offset -sizeof(m.count) - // Read start. It's at the offset -sizeof(m.count) -
// count*sizeof(indexEntry) - sizeof(m.start) // count*sizeof(indexEntry) - sizeof(m.start)
if _, err = r.ReadAt(b[8:], length-16-int64(m.count*8)); err != nil { if _, err = f.ReadAt(b[8:], m.length-16-int64(m.count*8)); err != nil {
return return
} }
m.start = binary.LittleEndian.Uint64(b[8:]) m.start = binary.LittleEndian.Uint64(b[8:])

View file

@ -18,6 +18,7 @@ package era
import ( import (
"bytes" "bytes"
"io"
"math/big" "math/big"
"os" "os"
"testing" "testing"
@ -71,43 +72,52 @@ func TestEra1Builder(t *testing.T) {
} }
// Verify Era1 contents. // Verify Era1 contents.
r, err := NewReader(f) e, err := Open(f.Name())
if err != nil { if err != nil {
t.Fatalf("failed to make reader: %s", err) t.Fatalf("failed to open era: %v", err)
}
it, err := NewRawIterator(e)
if err != nil {
t.Fatalf("failed to make iterator: %s", err)
} }
for i := uint64(0); i < uint64(len(chain.headers)); i++ { for i := uint64(0); i < uint64(len(chain.headers)); i++ {
if !it.Next() {
t.Fatalf("expected more entries")
}
if it.Error() != nil {
t.Fatalf("unexpected error %v", it.Error())
}
// Check headers. // Check headers.
header, err := r.readHeaderRLP(i) header, err := io.ReadAll(it.Header)
if err != nil { if err != nil {
t.Fatalf("error reading from era1: %v", err) t.Fatalf("error reading header: %v", err)
} }
if !bytes.Equal(header, chain.headers[i]) { if !bytes.Equal(header, chain.headers[i]) {
t.Fatalf("mismatched header: want %s, got %s", chain.headers[i], header) t.Fatalf("mismatched header: want %s, got %s", chain.headers[i], header)
} }
// Check bodies. // Check bodies.
body, err := r.readBodyRLP(i) body, err := io.ReadAll(it.Body)
if err != nil { if err != nil {
t.Fatalf("error reading from era1: %v", err) t.Fatalf("error reading body: %v", err)
} }
if !bytes.Equal(body, chain.bodies[i]) { if !bytes.Equal(body, chain.bodies[i]) {
t.Fatalf("mismatched body: want %s, got %s", chain.bodies[i], body) t.Fatalf("mismatched body: want %s, got %s", chain.bodies[i], body)
} }
// Check receipts. // Check receipts.
receipts, err := r.readReceiptsRLP(i) receipts, err := io.ReadAll(it.Receipts)
if err != nil { if err != nil {
t.Fatalf("error reading from era1: %v", err) t.Fatalf("error reading receipts: %v", err)
} }
if !bytes.Equal(receipts, chain.receipts[i]) { if !bytes.Equal(receipts, chain.receipts[i]) {
t.Fatalf("mismatched body: want %s, got %s", chain.receipts[i], receipts) t.Fatalf("mismatched receipts: want %s, got %s", chain.receipts[i], receipts)
} }
// Check total difficulty. // Check total difficulty.
td, err := r.readTotalDifficulty(i) rawTd, err := io.ReadAll(it.TotalDifficulty)
if err != nil { if err != nil {
t.Fatalf("error reading from era1: %v", err) t.Fatalf("error reading td: %v", err)
} }
td := new(big.Int).SetBytes(reverseOrder(rawTd))
if td.Cmp(chain.tds[i]) != 0 { if td.Cmp(chain.tds[i]) != 0 {
t.Fatalf("mismatched tds: want %s, got %s", chain.tds[i], td) t.Fatalf("mismatched tds: want %s, got %s", chain.tds[i], td)
} }

154
internal/era/iterator.go Normal file
View file

@ -0,0 +1,154 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package era
import (
"io"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
// Iterator wraps RawIterator and returns decoded Era1 entries.
type Iterator struct {
inner *RawIterator
}
// NewIterator returns a new Iterator instance.
func NewIterator(e *Era) (*Iterator, error) {
inner, err := NewRawIterator(e)
if err != nil {
return nil, err
}
return &Iterator{inner}, nil
}
// Next moves the iterator to the next block entry.
func (it *Iterator) Next() bool {
return it.inner.Next()
}
// Error returns the error status of the iterator.
func (it *Iterator) Error() error {
return it.inner.Error()
}
// Block returns the block for the iterator's current position.
func (it *Iterator) Block() (*types.Block, error) {
var (
header types.Header
body types.Body
)
if err := rlp.Decode(it.inner.Header, &header); err != nil {
return nil, err
}
if err := rlp.Decode(it.inner.Body, &body); err != nil {
return nil, err
}
return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil
}
// Receipts returns the receipts for the iterator's current position.
func (it *Iterator) Receipts() (types.Receipts, error) {
var receipts types.Receipts
err := rlp.Decode(it.inner.Receipts, &receipts)
return receipts, err
}
// BlockAndReceipts returns the block and receipts for the iterator's current
// position.
func (it *Iterator) BlockAndReceipts() (*types.Block, types.Receipts, error) {
b, err := it.Block()
if err != nil {
return nil, nil, err
}
r, err := it.Receipts()
if err != nil {
return nil, nil, err
}
return b, r, nil
}
// TotalDifficulty returns the total difficulty for the iterator's current
// position.
func (it *Iterator) TotalDifficulty() (*big.Int, error) {
var td []byte
if err := rlp.Decode(it.inner.TotalDifficulty, td); err != nil {
return nil, err
}
return new(big.Int).SetBytes(reverseOrder(td)), nil
}
// RawIterator reads an RLP-encode Era1 entries.
type RawIterator struct {
e *Era // backing Era1
curOffset int64 // offset of current block
next uint64 // next block to read
err error // last error
Header io.Reader
Body io.Reader
Receipts io.Reader
TotalDifficulty io.Reader
}
// NewRawIterator returns a new Iterator instance.
func NewRawIterator(e *Era) (*RawIterator, error) {
return &RawIterator{
e: e,
next: e.m.start,
}, nil
}
// Next moves the iterator to the next block entry.
func (it *RawIterator) Next() bool {
if it.e.m.start+it.e.m.count <= it.next {
return false
}
off, err := it.e.readOffset(it.next)
if err == io.EOF {
it.err = err
return false
}
var n int
if it.Header, n, it.err = newSnappyReader(it.e.s, off); it.err != nil {
return true
}
off += int64(n)
if it.Body, n, it.err = newSnappyReader(it.e.s, off); it.err != nil {
return true
}
off += int64(n)
if it.Receipts, n, it.err = newSnappyReader(it.e.s, off); it.err != nil {
return true
}
off += int64(n)
if it.TotalDifficulty, n, it.err = newReader(it.e.s, off); it.err != nil {
return true
}
it.next += 1
return true
}
// Error returns the error status of the iterator.
func (it *RawIterator) Error() error {
if it.err == io.EOF {
return nil
}
return it.err
}