From 5cc66c2f47efb66f4f5f4813abd26364ed1bb926 Mon Sep 17 00:00:00 2001 From: lightclient Date: Thu, 24 Jul 2025 14:00:38 -0600 Subject: [PATCH] all: refactor era1 and era2 into era subpackages of onedb and execdb --- cmd/era/main.go | 17 +- cmd/geth/chaincmd.go | 1 - cmd/utils/cmd.go | 25 +- cmd/utils/era_interface.go | 82 ----- cmd/utils/history_test.go | 12 +- core/rawdb/eradb/eradb.go | 17 +- internal/era/accumulator.go | 21 +- internal/era/era.go | 318 ++---------------- internal/era/eradl/eradl.go | 4 +- internal/{era2 => era/execdb}/builder.go | 39 +-- internal/{era2 => era/execdb}/era_test.go | 0 internal/{era2 => era/execdb}/iterator.go | 13 +- .../{era2/era.go => era/execdb/reader.go} | 73 ++-- internal/era/iterator.go | 196 ----------- internal/era/{ => onedb}/builder.go | 30 +- .../{era_test.go => onedb/builder_test.go} | 6 +- internal/era/onedb/iterator.go | 200 +++++++++++ internal/era/onedb/reader.go | 308 +++++++++++++++++ internal/{era2 => era}/proof.go | 2 +- internal/era2/accumulator.go | 91 ----- 20 files changed, 656 insertions(+), 799 deletions(-) delete mode 100644 cmd/utils/era_interface.go rename internal/{era2 => era/execdb}/builder.go (90%) rename internal/{era2 => era/execdb}/era_test.go (100%) rename internal/{era2 => era/execdb}/iterator.go (91%) rename internal/{era2/era.go => era/execdb/reader.go} (93%) rename internal/era/{ => onedb}/builder.go (89%) rename internal/era/{era_test.go => onedb/builder_test.go} (98%) create mode 100644 internal/era/onedb/iterator.go create mode 100644 internal/era/onedb/reader.go rename internal/{era2 => era}/proof.go (99%) delete mode 100644 internal/era2/accumulator.go diff --git a/cmd/era/main.go b/cmd/era/main.go index 35a889d4dc..e0ae8a18cd 100644 --- a/cmd/era/main.go +++ b/cmd/era/main.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/internal/era/onedb" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/params" @@ -53,7 +54,7 @@ var ( eraSizeFlag = &cli.IntFlag{ Name: "size", Usage: "number of blocks per era", - Value: era.MaxEra1Size, + Value: era.MaxSize, } txsFlag = &cli.BoolFlag{ Name: "txs", @@ -164,19 +165,19 @@ func info(ctx *cli.Context) error { } // open opens an era1 file at a certain epoch. -func open(ctx *cli.Context, epoch uint64) (*era.Era, error) { +func open(ctx *cli.Context, epoch uint64) (*onedb.Era, error) { var ( dir = ctx.String(dirFlag.Name) network = ctx.String(networkFlag.Name) ) - entries, err := era.ReadDir(dir, network) + entries, err := onedb.ReadDir(dir, network) if err != nil { return nil, fmt.Errorf("error reading era dir: %w", err) } if epoch >= uint64(len(entries)) { return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch) } - return era.Open(filepath.Join(dir, entries[epoch])) + return onedb.Open(filepath.Join(dir, entries[epoch])) } // verify checks each era1 file in a directory to ensure it is well-formed and @@ -198,7 +199,7 @@ func verify(ctx *cli.Context) error { reported = time.Now() ) - entries, err := era.ReadDir(dir, network) + entries, err := onedb.ReadDir(dir, network) if err != nil { return fmt.Errorf("error reading %s: %w", dir, err) } @@ -212,7 +213,7 @@ func verify(ctx *cli.Context) error { // Wrap in function so defers don't stack. err := func() error { name := entries[i] - e, err := era.Open(filepath.Join(dir, name)) + e, err := onedb.Open(filepath.Join(dir, name)) if err != nil { return fmt.Errorf("error opening era1 file %s: %w", name, err) } @@ -243,7 +244,7 @@ func verify(ctx *cli.Context) error { } // checkAccumulator verifies the accumulator matches the data in the Era. -func checkAccumulator(e *era.Era) error { +func checkAccumulator(e *onedb.Era) error { var ( err error want common.Hash @@ -257,7 +258,7 @@ func checkAccumulator(e *era.Era) error { if td, err = e.InitialTD(); err != nil { return fmt.Errorf("error reading total difficulty: %w", err) } - it, err := era.NewIterator(e) + it, err := onedb.NewIterator(e) if err != nil { return fmt.Errorf("error making era iterator: %w", err) } diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 9ec2e01b56..1ef0ffebe2 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -43,7 +43,6 @@ import ( "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era/eradl" - "github.com/ethereum/go-ethereum/internal/era2" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index cd53c4a51e..400fb92fd0 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -44,7 +44,8 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/debug" - "github.com/ethereum/go-ethereum/internal/era2" + era2 "github.com/ethereum/go-ethereum/internal/era/execdb" + "github.com/ethereum/go-ethereum/internal/era/onedb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -252,11 +253,11 @@ func readList(filename string) ([]string, error) { // ImportHistory imports Era1 files containing historical block information, // starting from genesis. The assumption is held that the provided chain // segment in Era1 file should all be canonical and verified. -func ImportHistory(chain *core.BlockChain, dir string, network string, format Format) error { +func ImportHistory(chain *core.BlockChain, dir string, network string) error { if chain.CurrentSnapBlock().Number.BitLen() != 0 { return errors.New("history import only supported when starting from genesis") } - entries, err := format.ReadDir(dir, network) + entries, err := onedb.ReadDir(dir, network) if err != nil { return fmt.Errorf("error reading %s: %w", dir, err) } @@ -299,12 +300,12 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, format Fo f.Close() return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, want) } - // rewind for reading - if _, err := f.Seek(0, io.SeekStart); err != nil { - f.Close() - return fmt.Errorf("rewind %s: %w", file, err) + // Import all block data from Era1. + e, err := onedb.From(f) + if err != nil { + return fmt.Errorf("error opening era: %w", err) } - it, err := format.NewIterator(f) + it, err := onedb.NewIterator(e) if err != nil { return fmt.Errorf("error creating iterator: %w", err) } @@ -515,7 +516,7 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las // ExportHistory exports blockchain history into the specified directory, // following the Era format. -func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64, f Format) error { +func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) error { log.Info("Exporting blockchain history", "dir", dir) if head := bc.CurrentBlock().Number.Uint64(); head < last { log.Warn("Last block beyond head, setting last = head", "head", head, "last", last) @@ -544,7 +545,7 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64, f for batch := first; batch <= last; batch += step { idx := int(batch / step) - tmpPath := filepath.Join(dir, f.Filename(network, idx, common.Hash{})) + tmpPath := filepath.Join(dir, onedb.Filename(network, idx, common.Hash{})) if err := func() error { fh, err := os.Create(tmpPath) @@ -553,7 +554,7 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64, f } defer fh.Close() - bldr := f.NewBuilder(fh) + bldr := onedb.NewBuilder(fh) for j := uint64(0); j < step && batch+j <= last; j++ { n := batch + j @@ -576,7 +577,7 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64, f if err != nil { return err } - final := filepath.Join(dir, f.Filename(network, idx, root)) + final := filepath.Join(dir, onedb.Filename(network, idx, root)) if err := os.Rename(tmpPath, final); err != nil { return err } diff --git a/cmd/utils/era_interface.go b/cmd/utils/era_interface.go deleted file mode 100644 index de3fbde707..0000000000 --- a/cmd/utils/era_interface.go +++ /dev/null @@ -1,82 +0,0 @@ -package utils - -import ( - "io" - "math/big" - "os" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/internal/era" - "github.com/ethereum/go-ethereum/internal/era2" -) - -// Iterator is the common, read‑only view returned by both Era‑1 and Era‑E. -type Iterator interface { - Next() bool - Number() uint64 - Block() (*types.Block, error) - Receipts() (types.Receipts, error) - Error() error -} - -// Builder (unchanged) – writes one archive file. -type Builder interface { - Add(blk *types.Block, rcpts types.Receipts, td *big.Int, proof era2.Proof) error - Finalize() (common.Hash, error) -} - -// Format now encapsulates *all* format‑specific behaviour, both read & write. -type Format interface { - // ----- writer side ----- - Filename(network string, epoch int, root common.Hash) string - NewBuilder(w io.Writer) Builder - - // ----- reader side ----- - ReadDir(dir, network string) ([]string, error) - NewIterator(f *os.File) (Iterator, error) -} - -type era1Builder struct{ *era.Builder } - -func (b *era1Builder) Add(blk *types.Block, rc types.Receipts, td *big.Int, proof era2.Proof) error { - return b.Builder.Add(blk, rc, td) -} - -type era1Format struct{} - -func (era1Format) Filename(n string, e int, h common.Hash) string { return era.Filename(n, e, h) } -func (era1Format) NewBuilder(w io.Writer) Builder { return &era1Builder{era.NewBuilder(w)} } -func (era1Format) ReadDir(dir, net string) ([]string, error) { return era.ReadDir(dir, net) } -func (era1Format) NewIterator(f *os.File) (Iterator, error) { - e, err := era.From(f) - if err != nil { - return nil, err - } - return era.NewIterator(e) -} - -var Era1 Format = era1Format{} - -type eraeBuilder struct{ *era2.Builder } - -func (b *eraeBuilder) Add(blk *types.Block, rc types.Receipts, td *big.Int, proof era2.Proof) error { - return b.Builder.Add(blk, rc, td, nil) // no proofs yet -} - -type eraeFormat struct{} - -func (eraeFormat) Filename(n string, e int, h common.Hash) string { return era2.Filename(n, e, h) } -func (eraeFormat) NewBuilder(w io.Writer) Builder { return &eraeBuilder{era2.NewBuilder(w)} } -func (eraeFormat) ReadDir(dir, net string) ([]string, error) { return era2.ReadDir(dir, net) } -func (eraeFormat) NewIterator(f *os.File) (Iterator, error) { - e, err := era2.From(f) - if err != nil { - return nil, err - } - return era2.NewIterator(e) -} - -var EraE Format = eraeFormat{} - -// Exported singleton diff --git a/cmd/utils/history_test.go b/cmd/utils/history_test.go index 10044a3f83..14e41795a8 100644 --- a/cmd/utils/history_test.go +++ b/cmd/utils/history_test.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/internal/era2" + "github.com/ethereum/go-ethereum/internal/era/onedb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/triedb" @@ -89,7 +89,7 @@ func TestHistoryImportAndExport(t *testing.T) { dir := t.TempDir() // Export history to temp directory. - if err := ExportHistory(chain, dir, 0, count, step, Era1); err != nil { + if err := ExportHistory(chain, dir, 0, count, step); err != nil { t.Fatalf("error exporting history: %v", err) } @@ -101,7 +101,7 @@ func TestHistoryImportAndExport(t *testing.T) { checksums := strings.Split(string(b), "\n") // Verify each Era. - entries, _ := era2.ReadDir(dir, "mainnet") + entries, _ := onedb.ReadDir(dir, "mainnet") for i, filename := range entries { func() { f, err := os.Open(filepath.Join(dir, filename)) @@ -118,12 +118,12 @@ func TestHistoryImportAndExport(t *testing.T) { 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) } - e, err := era2.From(f) + e, err := onedb.From(f) if err != nil { t.Fatalf("error opening era: %v", err) } defer e.Close() - it, err := era2.NewIterator(e) + it, err := onedb.NewIterator(e) if err != nil { t.Fatalf("error making era reader: %v", err) } @@ -170,7 +170,7 @@ func TestHistoryImportAndExport(t *testing.T) { if err != nil { t.Fatalf("unable to initialize chain: %v", err) } - if err := ImportHistory(imported, dir, "mainnet", EraE); err != nil { + if err := ImportHistory(imported, dir, "mainnet"); err != nil { t.Fatalf("failed to import chain: %v", err) } if have, want := imported.CurrentHeader(), chain.CurrentHeader(); have.Hash() != want.Hash() { diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go index a552b94da9..391c22b1a0 100644 --- a/core/rawdb/eradb/eradb.go +++ b/core/rawdb/eradb/eradb.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/internal/era/onedb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" ) @@ -51,7 +52,7 @@ type Store struct { type fileCacheEntry struct { refcount int // reference count. This is protected by Store.mu! opened chan struct{} // signals opening of file has completed - file *era.Era // the file + file *onedb.Era // the file err error // error from opening the file } @@ -102,7 +103,7 @@ func (db *Store) Close() { // GetRawBody returns the raw body for a given block number. func (db *Store) GetRawBody(number uint64) ([]byte, error) { - epoch := number / uint64(era.MaxEra1Size) + epoch := number / uint64(era.MaxSize) entry := db.getEraByEpoch(epoch) if entry.err != nil { if errors.Is(entry.err, fs.ErrNotExist) { @@ -117,7 +118,7 @@ func (db *Store) GetRawBody(number uint64) ([]byte, error) { // GetRawReceipts returns the raw receipts for a given block number. func (db *Store) GetRawReceipts(number uint64) ([]byte, error) { - epoch := number / uint64(era.MaxEra1Size) + epoch := number / uint64(era.MaxSize) entry := db.getEraByEpoch(epoch) if entry.err != nil { if errors.Is(entry.err, fs.ErrNotExist) { @@ -249,7 +250,7 @@ func (db *Store) getCacheEntry(epoch uint64) (stat fileCacheStatus, entry *fileC } // fileOpened is called after an era file has been successfully opened. -func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *era.Era) { +func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *onedb.Era) { db.mu.Lock() defer db.mu.Unlock() @@ -282,7 +283,7 @@ func (db *Store) fileFailedToOpen(epoch uint64, entry *fileCacheEntry, err error entry.err = err } -func (db *Store) openEraFile(epoch uint64) (*era.Era, error) { +func (db *Store) openEraFile(epoch uint64) (*onedb.Era, error) { // File name scheme is --. glob := fmt.Sprintf("*-%05d-*.era1", epoch) matches, err := filepath.Glob(filepath.Join(db.datadir, glob)) @@ -297,14 +298,14 @@ func (db *Store) openEraFile(epoch uint64) (*era.Era, error) { } filename := matches[0] - e, err := era.Open(filename) + e, err := onedb.Open(filename) if err != nil { return nil, err } // Sanity-check start block. - if e.Start()%uint64(era.MaxEra1Size) != 0 { + if e.Start()%uint64(era.MaxSize) != 0 { e.Close() - return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxEra1Size) + return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxSize) } log.Debug("Opened era1 file", "epoch", epoch) return e, nil diff --git a/internal/era/accumulator.go b/internal/era/accumulator.go index 83a761f1fd..72e36fe00f 100644 --- a/internal/era/accumulator.go +++ b/internal/era/accumulator.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "math/big" + "slices" "github.com/ethereum/go-ethereum/common" ssz "github.com/ferranbt/fastssz" @@ -31,8 +32,8 @@ func ComputeAccumulator(hashes []common.Hash, tds []*big.Int) (common.Hash, erro if len(hashes) != len(tds) { return common.Hash{}, errors.New("must have equal number hashes as td values") } - if len(hashes) > MaxEra1Size { - return common.Hash{}, fmt.Errorf("too many records: have %d, max %d", len(hashes), MaxEra1Size) + if len(hashes) > MaxSize { + return common.Hash{}, fmt.Errorf("too many records: have %d, max %d", len(hashes), MaxSize) } hh := ssz.NewHasher() for i := range hashes { @@ -43,7 +44,7 @@ func ComputeAccumulator(hashes []common.Hash, tds []*big.Int) (common.Hash, erro } hh.Append(root[:]) } - hh.MerkleizeWithMixin(0, uint64(len(hashes)), uint64(MaxEra1Size)) + hh.MerkleizeWithMixin(0, uint64(len(hashes)), uint64(MaxSize)) return hh.HashRoot() } @@ -69,23 +70,15 @@ func (h *headerRecord) HashTreeRoot() ([32]byte, error) { // HashTreeRootWith ssz hashes the headerRecord object with a hasher. func (h *headerRecord) HashTreeRootWith(hh ssz.HashWalker) (err error) { hh.PutBytes(h.Hash[:]) - td := bigToBytes32(h.TotalDifficulty) + td := BigToBytes32(h.TotalDifficulty) hh.PutBytes(td[:]) hh.Merkleize(0) return } // bigToBytes32 converts a big.Int into a little-endian 32-byte array. -func bigToBytes32(n *big.Int) (b [32]byte) { +func BigToBytes32(n *big.Int) (b [32]byte) { n.FillBytes(b[:]) - reverseOrder(b[:]) + slices.Reverse(b[:]) return } - -// reverseOrder reverses the byte order of a slice. -func reverseOrder(b []byte) []byte { - for i := 0; i < 16; i++ { - b[i], b[32-i-1] = b[32-i-1], b[i] - } - return b -} diff --git a/internal/era/era.go b/internal/era/era.go index 118c67abfd..cab3f458a4 100644 --- a/internal/era/era.go +++ b/internal/era/era.go @@ -1,317 +1,47 @@ -// Copyright 2024 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - package era import ( - "encoding/binary" - "fmt" "io" "math/big" - "os" - "path" - "strconv" - "strings" - "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/internal/era/e2store" - "github.com/ethereum/go-ethereum/rlp" - "github.com/golang/snappy" ) +// Type constants for the e2store entries in the Era1 and EraE formats. var ( - TypeVersion uint16 = 0x3265 - TypeCompressedHeader uint16 = 0x03 - TypeCompressedBody uint16 = 0x04 - TypeCompressedReceipts uint16 = 0x05 - TypeTotalDifficulty uint16 = 0x06 - TypeAccumulator uint16 = 0x07 - TypeBlockIndex uint16 = 0x3266 + TypeVersion uint16 = 0x3265 + TypeCompressedHeader uint16 = 0x03 + TypeCompressedBody uint16 = 0x04 + TypeCompressedReceipts uint16 = 0x05 + TypeTotalDifficulty uint16 = 0x06 + TypeAccumulator uint16 = 0x07 + TypeCompressedSlimReceipts uint16 = 0x08 // uses eth/69 encoding + TypeProof uint16 = 0x09 + TypeBlockIndex uint16 = 0x3266 + TypeComponentIndex uint16 = 0x3267 - MaxEra1Size = 8192 + MaxSize = 8192 + // headerSize uint64 = 8 ) -// Filename returns a recognizable Era1-formatted file name for the specified -// epoch and network. -func Filename(network string, epoch int, root common.Hash) string { - return fmt.Sprintf("%s-%05d-%s.era1", network, epoch, root.Hex()[2:10]) -} - -// ReadDir reads all the era1 files in a directory for a given network. -// Format: --.era1 -func ReadDir(dir, network string) ([]string, error) { - entries, err := os.ReadDir(dir) - if err != nil { - return nil, fmt.Errorf("error reading directory %s: %w", dir, err) - } - var ( - next = uint64(0) - eras []string - ) - for _, entry := range entries { - if path.Ext(entry.Name()) != ".era1" { - continue - } - parts := strings.Split(entry.Name(), "-") - if len(parts) != 3 || parts[0] != network { - // Invalid era1 filename, skip. - continue - } - if epoch, err := strconv.ParseUint(parts[1], 10, 64); err != nil { - return nil, fmt.Errorf("malformed era1 filename: %s", entry.Name()) - } else if epoch != next { - return nil, fmt.Errorf("missing epoch %d", next) - } - next += 1 - eras = append(eras, entry.Name()) - } - return eras, nil -} - type ReadAtSeekCloser interface { io.ReaderAt io.Seeker io.Closer } -// Era reads and Era1 file. -type Era struct { - 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 +// Iterator represents the iterator interface for various types of era stores. +type Iterator interface { + Next() bool + Number() uint64 + Block() (*types.Block, error) + Receipts() (types.Receipts, error) + Error() error } -// From returns an Era backed by f. -func From(f ReadAtSeekCloser) (*Era, error) { - m, err := readMetadata(f) - if err != nil { - return nil, err - } - return &Era{ - f: f, - s: e2store.NewReader(f), - m: m, - mu: new(sync.Mutex), - }, nil -} - -// Open returns an Era backed by the given filename. -func Open(filename string) (*Era, error) { - f, err := os.Open(filename) - if err != nil { - return nil, err - } - return From(f) -} - -func (e *Era) Close() error { - return e.f.Close() -} - -// GetBlockByNumber returns the block for the given block number. -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: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count) - } - off, err := e.readOffset(num) - if err != nil { - return nil, err - } - r, n, err := newSnappyReader(e.s, TypeCompressedHeader, off) - if err != nil { - return nil, err - } - var header types.Header - if err := rlp.Decode(r, &header); err != nil { - return nil, err - } - off += n - r, _, err = newSnappyReader(e.s, TypeCompressedBody, off) - if err != nil { - return nil, err - } - var body types.Body - if err := rlp.Decode(r, &body); err != nil { - return nil, err - } - return types.NewBlockWithHeader(&header).WithBody(body), nil -} - -// GetRawBodyByNumber returns the RLP-encoded body for the given block number. -func (e *Era) GetRawBodyByNumber(num uint64) ([]byte, error) { - if e.m.start > num || e.m.start+e.m.count <= num { - return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count) - } - off, err := e.readOffset(num) - if err != nil { - return nil, err - } - off, err = e.s.SkipN(off, 1) - if err != nil { - return nil, err - } - r, _, err := newSnappyReader(e.s, TypeCompressedBody, off) - if err != nil { - return nil, err - } - return io.ReadAll(r) -} - -// GetRawReceiptsByNumber returns the RLP-encoded receipts for the given block number. -func (e *Era) GetRawReceiptsByNumber(num uint64) ([]byte, error) { - if e.m.start > num || e.m.start+e.m.count <= num { - return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count) - } - off, err := e.readOffset(num) - if err != nil { - return nil, err - } - - // Skip over header and body. - off, err = e.s.SkipN(off, 2) - if err != nil { - return nil, err - } - - r, _, err := newSnappyReader(e.s, TypeCompressedReceipts, off) - if err != nil { - return nil, err - } - return io.ReadAll(r) -} - -// Accumulator reads the accumulator entry in the Era1 file. -func (e *Era) Accumulator() (common.Hash, error) { - entry, err := e.s.Find(TypeAccumulator) - if err != nil { - return common.Hash{}, err - } - return common.BytesToHash(entry.Value), nil -} - -// InitialTD returns initial total difficulty before the difficulty of the -// first block of the Era1 is applied. -func (e *Era) InitialTD() (*big.Int, error) { - var ( - r io.Reader - header types.Header - rawTd []byte - n int64 - off int64 - err error - ) - - // Read first header. - if off, err = e.readOffset(e.m.start); err != nil { - return nil, err - } - if r, n, err = newSnappyReader(e.s, TypeCompressedHeader, off); err != nil { - return nil, err - } - if err := rlp.Decode(r, &header); err != nil { - return nil, err - } - off += n - - // Skip over header and body. - off, err = e.s.SkipN(off, 2) - if err != nil { - return nil, err - } - - // Read total difficulty after first block. - if r, _, err = e.s.ReaderAt(TypeTotalDifficulty, off); err != nil { - return nil, err - } - rawTd, err = io.ReadAll(r) - if 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. -func (e *Era) Start() uint64 { - return e.m.start -} - -// Count returns the total number of blocks in the Era1. -func (e *Era) Count() uint64 { - 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 ( - blockIndexRecordOffset = e.m.length - 24 - int64(e.m.count)*8 // skips start, count, and header - firstIndex = blockIndexRecordOffset + 16 // first index after header / start-num - indexOffset = int64(n-e.m.start) * 8 // desired index * size of indexes - offOffset = firstIndex + indexOffset // offset of block offset - ) - e.mu.Lock() - defer e.mu.Unlock() - clear(e.buf[:]) - if _, err := e.f.ReadAt(e.buf[:], offOffset); err != nil { - return 0, err - } - // Since the block offset is relative from the start of the block index record - // we need to add the record offset to it's offset to get the block's absolute - // offset. - return blockIndexRecordOffset + int64(binary.LittleEndian.Uint64(e.buf[:])), nil -} - -// newSnappyReader returns a snappy.Reader for the e2store entry value at off. -func newSnappyReader(e *e2store.Reader, expectedType uint16, off int64) (io.Reader, int64, error) { - r, n, err := e.ReaderAt(expectedType, off) - if err != nil { - return nil, 0, err - } - return snappy.NewReader(r), int64(n), err -} - -// metadata wraps the metadata in the block index. -type metadata struct { - start uint64 - count uint64 - length int64 -} - -// readMetadata reads the metadata stored in an Era1 file's block index. -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) - // Read count. It's the last 8 bytes of the file. - if _, err = f.ReadAt(b[:8], m.length-8); err != nil { - return - } - m.count = binary.LittleEndian.Uint64(b) - // Read start. It's at the offset -sizeof(m.count) - - // count*sizeof(indexEntry) - sizeof(m.start) - if _, err = f.ReadAt(b[8:], m.length-16-int64(m.count*8)); err != nil { - return - } - m.start = binary.LittleEndian.Uint64(b[8:]) - return +// Builder represents the interface for various types of era formats. +type Builder interface { + Add(block *types.Block, receipts types.Receipts, td *big.Int, proof []byte) error + Finalize() (common.Hash, error) } diff --git a/internal/era/eradl/eradl.go b/internal/era/eradl/eradl.go index 30bd2bc0d5..375b9ad15b 100644 --- a/internal/era/eradl/eradl.go +++ b/internal/era/eradl/eradl.go @@ -86,8 +86,8 @@ func (l *Loader) DownloadAll(destDir string) error { // DownloadBlockRange fetches the era1 files for the given block range. func (l *Loader) DownloadBlockRange(start, end uint64, destDir string) error { - startEpoch := start / uint64(era.MaxEra1Size) - endEpoch := end / uint64(era.MaxEra1Size) + startEpoch := start / uint64(era.MaxSize) + endEpoch := end / uint64(era.MaxSize) return l.DownloadEpochRange(startEpoch, endEpoch, destDir) } diff --git a/internal/era2/builder.go b/internal/era/execdb/builder.go similarity index 90% rename from internal/era2/builder.go rename to internal/era/execdb/builder.go index 740015df08..4f184685d2 100644 --- a/internal/era2/builder.go +++ b/internal/era/execdb/builder.go @@ -52,25 +52,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era/e2store" "github.com/ethereum/go-ethereum/rlp" "github.com/golang/snappy" ) -// Type constants for the e2store entries in the Era2 format. -const ( - TypeVersion uint16 = 0x3265 - TypeCompressedHeader uint16 = 0x08 - TypeCompressedBody uint16 = 0x09 - TypeCompressedReceipts uint16 = 0x0a - TypeTotalDifficulty uint16 = 0x0b - TypeProof uint16 = 0x0c - TypeAccumulatorRoot uint16 = 0x0d - TypeBlockIndex uint16 = 0x3267 - MaxEraESize int = 8192 - headerSize uint64 = 8 -) - // Temporary buffer for writing blocks until the Finalize method is called. type buffer struct { headers [][]byte @@ -113,7 +100,7 @@ func NewBuilder(w io.Writer) *Builder { } // Add writes a block entry, its reciepts, and optionally its proofs as well into the e2store file. -func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int, proof Proof) error { +func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int, proof era.Proof) error { header := block.Header() body := block.Body() if len(b.buff.headers) == 0 { // first block determines wether proofs are expected @@ -156,8 +143,8 @@ func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int, // AddRLP takes the RLP encoded block components and writes them to the underlying e2store file. func (b *Builder) AddRLP(headerRLP []byte, bodyRLP []byte, receipts []byte, proof []byte, blockNum uint64, blockHash common.Hash, td *big.Int) error { - if len(b.buff.headers) >= MaxEraESize { - return fmt.Errorf("exceeds MaxEraESize %d", MaxEraESize) + if len(b.buff.headers) >= era.MaxSize { + return fmt.Errorf("exceeds max size %d", era.MaxSize) } b.buff.headers = append(b.buff.headers, headerRLP) @@ -173,7 +160,7 @@ func (b *Builder) AddRLP(headerRLP []byte, bodyRLP []byte, receipts []byte, proo if b.startNum == nil { b.startNum = new(uint64) *b.startNum = blockNum - if n, err := b.w.Write(TypeVersion, nil); err != nil { + if n, err := b.w.Write(era.TypeVersion, nil); err != nil { return fmt.Errorf("write version entry: %w", err) } else { b.written += uint64(n) @@ -189,7 +176,7 @@ func (b *Builder) Finalize() (common.Hash, error) { return common.Hash{}, errors.New("no blocks added, cannot finalize") } for _, data := range b.buff.headers { - off, err := b.addEntry(TypeCompressedHeader, data, true) + off, err := b.addEntry(era.TypeCompressedHeader, data, true) if err != nil { return common.Hash{}, fmt.Errorf("headers: %w", err) } @@ -197,7 +184,7 @@ func (b *Builder) Finalize() (common.Hash, error) { } for _, data := range b.buff.bodies { - off, err := b.addEntry(TypeCompressedBody, data, true) + off, err := b.addEntry(era.TypeCompressedBody, data, true) if err != nil { return common.Hash{}, fmt.Errorf("bodies: %w", err) } @@ -205,7 +192,7 @@ func (b *Builder) Finalize() (common.Hash, error) { } for _, data := range b.buff.receipts { - off, err := b.addEntry(TypeCompressedReceipts, data, true) + off, err := b.addEntry(era.TypeCompressedSlimReceipts, data, true) if err != nil { return common.Hash{}, fmt.Errorf("receipts: %w", err) } @@ -215,7 +202,7 @@ func (b *Builder) Finalize() (common.Hash, error) { if len(b.buff.tds) > 0 { for _, data := range b.buff.tds { littleEndian := uint256LE(data) - off, err := b.addEntry(TypeTotalDifficulty, littleEndian, false) + off, err := b.addEntry(era.TypeTotalDifficulty, littleEndian, false) if err != nil { return common.Hash{}, fmt.Errorf("total-difficulty: %w", err) } @@ -225,7 +212,7 @@ func (b *Builder) Finalize() (common.Hash, error) { if len(b.buff.proofs) > 0 { for _, data := range b.buff.proofs { - off, err := b.addEntry(TypeProof, data, true) + off, err := b.addEntry(era.TypeProof, data, true) if err != nil { return common.Hash{}, fmt.Errorf("proofs: %w", err) } @@ -236,11 +223,11 @@ func (b *Builder) Finalize() (common.Hash, error) { var accRoot common.Hash if len(b.hashes) > 0 { var err error - accRoot, err = ComputeAccumulator(b.hashes, b.buff.tds) + accRoot, err = era.ComputeAccumulator(b.hashes, b.buff.tds) if err != nil { return common.Hash{}, fmt.Errorf("compute accumulator: %w", err) } - if n, err := b.w.Write(TypeAccumulatorRoot, accRoot[:]); err != nil { + if n, err := b.w.Write(era.TypeAccumulator, accRoot[:]); err != nil { return common.Hash{}, fmt.Errorf("write accumulator: %w", err) } else { b.written += uint64(n) @@ -332,7 +319,7 @@ func (b *Builder) writeIndex() error { binary.LittleEndian.PutUint64(index[end+0:], componentCount) binary.LittleEndian.PutUint64(index[end+8:], count) - if n, err := b.w.Write(TypeBlockIndex, index); err != nil { + if n, err := b.w.Write(era.TypeComponentIndex, index); err != nil { return err } else { b.written += uint64(n) diff --git a/internal/era2/era_test.go b/internal/era/execdb/era_test.go similarity index 100% rename from internal/era2/era_test.go rename to internal/era/execdb/era_test.go diff --git a/internal/era2/iterator.go b/internal/era/execdb/iterator.go similarity index 91% rename from internal/era2/iterator.go rename to internal/era/execdb/iterator.go index 316825cfd9..d5ac3ab1d2 100644 --- a/internal/era2/iterator.go +++ b/internal/era/execdb/iterator.go @@ -20,8 +20,10 @@ import ( "errors" "io" "math/big" + "slices" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era/e2store" "github.com/ethereum/go-ethereum/rlp" "github.com/klauspost/compress/snappy" @@ -100,7 +102,8 @@ func (it *Iterator) TotalDifficulty() (*big.Int, error) { if err != nil { return nil, err } - return new(big.Int).SetBytes(reverseOrder(tdBytes)), nil + slices.Reverse(tdBytes) + return new(big.Int).SetBytes(tdBytes), nil } // ----------------------------------------------------------------------------- @@ -137,7 +140,7 @@ func (it *RawIterator) Next() bool { it.setErr(err) return false } - it.Header, _, err = newSnappyReader(it.e.s, TypeCompressedHeader, int64(headerOffset)) + it.Header, _, err = newSnappyReader(it.e.s, era.TypeCompressedHeader, int64(headerOffset)) if err != nil { it.setErr(err) return false @@ -148,7 +151,7 @@ func (it *RawIterator) Next() bool { it.setErr(err) return false } - it.Body, _, err = newSnappyReader(it.e.s, TypeCompressedBody, int64(bodyOffset)) + it.Body, _, err = newSnappyReader(it.e.s, era.TypeCompressedBody, int64(bodyOffset)) if err != nil { it.setErr(err) return false @@ -159,7 +162,7 @@ func (it *RawIterator) Next() bool { it.setErr(err) return false } - it.Receipts, _, err = newSnappyReader(it.e.s, TypeCompressedReceipts, int64(receiptsOffset)) + it.Receipts, _, err = newSnappyReader(it.e.s, era.TypeCompressedReceipts, int64(receiptsOffset)) if err != nil { it.setErr(err) return false @@ -171,7 +174,7 @@ func (it *RawIterator) Next() bool { it.setErr(err) return false } - it.TotalDifficulty, _, err = it.e.s.ReaderAt(TypeTotalDifficulty, int64(tdOffset)) + it.TotalDifficulty, _, err = it.e.s.ReaderAt(era.TypeTotalDifficulty, int64(tdOffset)) if err != nil { it.setErr(err) return false diff --git a/internal/era2/era.go b/internal/era/execdb/reader.go similarity index 93% rename from internal/era2/era.go rename to internal/era/execdb/reader.go index cc55de5489..e5a5bf0483 100644 --- a/internal/era2/era.go +++ b/internal/era/execdb/reader.go @@ -23,45 +23,21 @@ import ( "math/big" "os" "path" + "slices" "strconv" "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era/e2store" "github.com/ethereum/go-ethereum/rlp" "github.com/klauspost/compress/snappy" ) -// metadata contains the information about the era file that is written into the file. -type metadata struct { - start uint64 // start block number - count uint64 // number of blocks in the era - components uint64 // number of properties - length int64 // length of the file in bytes -} - -// componentType represents the integer form of a specific type that can be present in the era file. -type componentType int - -// TypeCompressedHeader, TypeCompressedBody, TypeCompressedReceipts, TypeTotalDifficulty, and TypeProof are the different types of components that can be present in the era file. -const ( - header componentType = iota - body - receipts - td - proof -) - -type ReadAtSeekCloser interface { - io.ReaderAt - io.Seeker - io.Closer -} - // Era object represents an era file that contains blocks and their components. type Era struct { - f ReadAtSeekCloser + f era.ReadAtSeekCloser s *e2store.Reader m metadata // metadata for the Era file } @@ -96,7 +72,7 @@ func (e *Era) Close() error { } // From returns an Era backed by f. -func From(f ReadAtSeekCloser) (*Era, error) { +func From(f era.ReadAtSeekCloser) (*Era, error) { e := &Era{f: f, s: e2store.NewReader(f)} if err := e.loadIndex(); err != nil { f.Close() @@ -166,7 +142,7 @@ func (e *Era) GetHeader(num uint64) (*types.Header, error) { return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedHeader, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeCompressedHeader, int64(off)) if err != nil { return nil, err } @@ -183,7 +159,7 @@ func (e *Era) GetBody(num uint64) (*types.Body, error) { return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedBody, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeCompressedBody, int64(off)) if err != nil { return nil, err } @@ -199,12 +175,13 @@ func (e *Era) getTD(blockNum uint64) (*big.Int, error) { if err != nil { return nil, err } - r, _, err := e.s.ReaderAt(TypeTotalDifficulty, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeTotalDifficulty, int64(off)) if err != nil { return nil, err } buf, _ := io.ReadAll(r) - td := new(big.Int).SetBytes(reverseOrder(buf)) + slices.Reverse(buf) + td := new(big.Int).SetBytes(buf) return td, nil } @@ -214,7 +191,7 @@ func (e *Era) GetRawBodyFrameByNumber(blockNum uint64) ([]byte, error) { if err != nil { return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedBody, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeCompressedBody, int64(off)) if err != nil { return nil, err } @@ -227,7 +204,7 @@ func (e *Era) GetRawReceiptsFrameByNumber(blockNum uint64) ([]byte, error) { if err != nil { return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedReceipts, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeCompressedSlimReceipts, int64(off)) if err != nil { return nil, err } @@ -240,7 +217,7 @@ func (e *Era) GetRawProofFrameByNumber(blockNum uint64) ([]byte, error) { if err != nil { return nil, err } - r, _, err := e.s.ReaderAt(TypeProof, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeProof, int64(off)) if err != nil { return nil, err } @@ -319,7 +296,7 @@ func (e *Era) GetHeaders(first, count uint64) ([]*types.Header, error) { if err != nil { return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedHeader, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeCompressedHeader, int64(off)) if err != nil { return nil, err } @@ -348,7 +325,7 @@ func (e *Era) GetBodies(first, count uint64) ([]*types.Body, error) { if err != nil { return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedBody, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeCompressedBody, int64(off)) if err != nil { return nil, err } @@ -377,7 +354,7 @@ func (e *Era) GetReceipts(first, count uint64) ([]types.Receipts, error) { if err != nil { return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedReceipts, int64(off)) + r, _, err := e.s.ReaderAt(era.TypeCompressedSlimReceipts, int64(off)) if err != nil { return nil, err } @@ -389,3 +366,23 @@ func (e *Era) GetReceipts(first, count uint64) ([]types.Receipts, error) { } return out, nil } + +// metadata contains the information about the era file that is written into the file. +type metadata struct { + start uint64 // start block number + count uint64 // number of blocks in the era + components uint64 // number of properties + length int64 // length of the file in bytes +} + +// componentType represents the integer form of a specific type that can be present in the era file. +type componentType int + +// TypeCompressedHeader, TypeCompressedBody, TypeCompressedReceipts, TypeTotalDifficulty, and TypeProof are the different types of components that can be present in the era file. +const ( + header componentType = iota + body + receipts + td + proof +) diff --git a/internal/era/iterator.go b/internal/era/iterator.go index 3c4f82d850..3da3a85d4d 100644 --- a/internal/era/iterator.go +++ b/internal/era/iterator.go @@ -1,197 +1 @@ -// Copyright 2024 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - package era - -import ( - "errors" - "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. Next must be immediately -// called on new iterators to load the first item. -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. It returns false when all -// items have been read or an error has halted its progress. Block, Receipts, -// and BlockAndReceipts should no longer be called after false is returned. -func (it *Iterator) Next() bool { - return it.inner.Next() -} - -// Number returns the current number block the iterator will return. -func (it *Iterator) Number() uint64 { - return it.inner.next - 1 -} - -// Error returns the error status of the iterator. It should be called before -// reading from any of the iterator's values. -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) { - if it.inner.Header == nil || it.inner.Body == nil { - return nil, errors.New("header and body must be non-nil") - } - 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), nil -} - -// Receipts returns the receipts for the iterator's current position. -func (it *Iterator) Receipts() (types.Receipts, error) { - if it.inner.Receipts == nil { - return nil, errors.New("receipts must be non-nil") - } - 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) { - td, err := io.ReadAll(it.inner.TotalDifficulty) - if 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 - 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 RawIterator instance. Next must be immediately -// called on new iterators to load the first item. -func NewRawIterator(e *Era) (*RawIterator, error) { - return &RawIterator{ - e: e, - next: e.m.start, - }, nil -} - -// Next moves the iterator to the next block entry. It returns false when all -// items have been read or an error has halted its progress. Header, Body, -// Receipts, TotalDifficulty will be set to nil in the case returning false or -// finding an error and should therefore no longer be read from. -func (it *RawIterator) Next() bool { - // Clear old errors. - it.err = nil - if it.e.m.start+it.e.m.count <= it.next { - it.clear() - return false - } - off, err := it.e.readOffset(it.next) - if err != nil { - // Error here means block index is corrupted, so don't - // continue. - it.clear() - it.err = err - return false - } - var n int64 - if it.Header, n, it.err = newSnappyReader(it.e.s, TypeCompressedHeader, off); it.err != nil { - it.clear() - return true - } - off += n - if it.Body, n, it.err = newSnappyReader(it.e.s, TypeCompressedBody, off); it.err != nil { - it.clear() - return true - } - off += n - if it.Receipts, n, it.err = newSnappyReader(it.e.s, TypeCompressedReceipts, off); it.err != nil { - it.clear() - return true - } - off += n - if it.TotalDifficulty, _, it.err = it.e.s.ReaderAt(TypeTotalDifficulty, off); it.err != nil { - it.clear() - return true - } - it.next += 1 - return true -} - -// Number returns the current number block the iterator will return. -func (it *RawIterator) Number() uint64 { - return it.next - 1 -} - -// Error returns the error status of the iterator. It should be called before -// reading from any of the iterator's values. -func (it *RawIterator) Error() error { - if it.err == io.EOF { - return nil - } - return it.err -} - -// clear sets all the outputs to nil. -func (it *RawIterator) clear() { - it.Header = nil - it.Body = nil - it.Receipts = nil - it.TotalDifficulty = nil -} diff --git a/internal/era/builder.go b/internal/era/onedb/builder.go similarity index 89% rename from internal/era/builder.go rename to internal/era/onedb/builder.go index 975561564c..b07ed328a7 100644 --- a/internal/era/builder.go +++ b/internal/era/onedb/builder.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package era +package onedb import ( "bytes" @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era/e2store" "github.com/ethereum/go-ethereum/rlp" "github.com/golang/snappy" @@ -96,7 +97,10 @@ func NewBuilder(w io.Writer) *Builder { // Add writes a compressed block entry and compressed receipts entry to the // underlying e2store file. -func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int) error { +func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int, proof era.Proof) error { + if proof != nil { + return fmt.Errorf("proof not allowed in era1 format") + } eh, err := rlp.EncodeToBytes(block.Header()) if err != nil { return err @@ -117,7 +121,7 @@ func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int) func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, hash common.Hash, td, difficulty *big.Int) error { // Write Era1 version entry before first block. if b.startNum == nil { - n, err := b.w.Write(TypeVersion, nil) + n, err := b.w.Write(era.TypeVersion, nil) if err != nil { return err } @@ -126,8 +130,8 @@ func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, hash comm b.startTd = new(big.Int).Sub(td, difficulty) b.written += n } - if len(b.indexes) >= MaxEra1Size { - return fmt.Errorf("exceeds maximum batch size of %d", MaxEra1Size) + if len(b.indexes) >= era.MaxSize { + return fmt.Errorf("exceeds maximum batch size of %d", era.MaxSize) } b.indexes = append(b.indexes, uint64(b.written)) @@ -135,19 +139,19 @@ func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, hash comm b.tds = append(b.tds, td) // Write block data. - if err := b.snappyWrite(TypeCompressedHeader, header); err != nil { + if err := b.snappyWrite(era.TypeCompressedHeader, header); err != nil { return err } - if err := b.snappyWrite(TypeCompressedBody, body); err != nil { + if err := b.snappyWrite(era.TypeCompressedBody, body); err != nil { return err } - if err := b.snappyWrite(TypeCompressedReceipts, receipts); err != nil { + if err := b.snappyWrite(era.TypeCompressedReceipts, receipts); err != nil { return err } // Also write total difficulty, but don't snappy encode. - btd := bigToBytes32(td) - n, err := b.w.Write(TypeTotalDifficulty, btd[:]) + btd := era.BigToBytes32(td) + n, err := b.w.Write(era.TypeTotalDifficulty, btd[:]) b.written += n if err != nil { return err @@ -163,11 +167,11 @@ func (b *Builder) Finalize() (common.Hash, error) { return common.Hash{}, errors.New("finalize called on empty builder") } // Compute accumulator root and write entry. - root, err := ComputeAccumulator(b.hashes, b.tds) + root, err := era.ComputeAccumulator(b.hashes, b.tds) if err != nil { return common.Hash{}, fmt.Errorf("error calculating accumulator root: %w", err) } - n, err := b.w.Write(TypeAccumulator, root[:]) + n, err := b.w.Write(era.TypeAccumulator, root[:]) b.written += n if err != nil { return common.Hash{}, fmt.Errorf("error writing accumulator: %w", err) @@ -196,7 +200,7 @@ func (b *Builder) Finalize() (common.Hash, error) { binary.LittleEndian.PutUint64(index[8+count*8:], uint64(count)) // Finally, write the block index entry. - if _, err := b.w.Write(TypeBlockIndex, index); err != nil { + if _, err := b.w.Write(era.TypeBlockIndex, index); err != nil { return common.Hash{}, fmt.Errorf("unable to write block index: %w", err) } diff --git a/internal/era/era_test.go b/internal/era/onedb/builder_test.go similarity index 98% rename from internal/era/era_test.go rename to internal/era/onedb/builder_test.go index 31fa0076a6..b1d9a539d6 100644 --- a/internal/era/era_test.go +++ b/internal/era/onedb/builder_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package era +package onedb import ( "bytes" @@ -22,6 +22,7 @@ import ( "io" "math/big" "os" + "slices" "testing" "github.com/ethereum/go-ethereum/common" @@ -136,7 +137,8 @@ func TestEra1Builder(t *testing.T) { if err != nil { t.Fatalf("error reading td: %v", err) } - td := new(big.Int).SetBytes(reverseOrder(rawTd)) + slices.Reverse(rawTd) + td := new(big.Int).SetBytes(rawTd) if td.Cmp(chain.tds[i]) != 0 { t.Fatalf("mismatched tds: want %s, got %s", chain.tds[i], td) } diff --git a/internal/era/onedb/iterator.go b/internal/era/onedb/iterator.go new file mode 100644 index 0000000000..558581e32b --- /dev/null +++ b/internal/era/onedb/iterator.go @@ -0,0 +1,200 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package onedb + +import ( + "errors" + "io" + "math/big" + "slices" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/era" + "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. Next must be immediately +// called on new iterators to load the first item. +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. It returns false when all +// items have been read or an error has halted its progress. Block, Receipts, +// and BlockAndReceipts should no longer be called after false is returned. +func (it *Iterator) Next() bool { + return it.inner.Next() +} + +// Number returns the current number block the iterator will return. +func (it *Iterator) Number() uint64 { + return it.inner.next - 1 +} + +// Error returns the error status of the iterator. It should be called before +// reading from any of the iterator's values. +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) { + if it.inner.Header == nil || it.inner.Body == nil { + return nil, errors.New("header and body must be non-nil") + } + 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), nil +} + +// Receipts returns the receipts for the iterator's current position. +func (it *Iterator) Receipts() (types.Receipts, error) { + if it.inner.Receipts == nil { + return nil, errors.New("receipts must be non-nil") + } + 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) { + td, err := io.ReadAll(it.inner.TotalDifficulty) + if err != nil { + return nil, err + } + slices.Reverse(td) + return new(big.Int).SetBytes(td), nil +} + +// RawIterator reads an RLP-encode Era1 entries. +type RawIterator struct { + e *Era // backing Era1 + 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 RawIterator instance. Next must be immediately +// called on new iterators to load the first item. +func NewRawIterator(e *Era) (*RawIterator, error) { + return &RawIterator{ + e: e, + next: e.m.start, + }, nil +} + +// Next moves the iterator to the next block entry. It returns false when all +// items have been read or an error has halted its progress. Header, Body, +// Receipts, TotalDifficulty will be set to nil in the case returning false or +// finding an error and should therefore no longer be read from. +func (it *RawIterator) Next() bool { + // Clear old errors. + it.err = nil + if it.e.m.start+it.e.m.count <= it.next { + it.clear() + return false + } + off, err := it.e.readOffset(it.next) + if err != nil { + // Error here means block index is corrupted, so don't + // continue. + it.clear() + it.err = err + return false + } + var n int64 + if it.Header, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedHeader, off); it.err != nil { + it.clear() + return true + } + off += n + if it.Body, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedBody, off); it.err != nil { + it.clear() + return true + } + off += n + if it.Receipts, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedReceipts, off); it.err != nil { + it.clear() + return true + } + off += n + if it.TotalDifficulty, _, it.err = it.e.s.ReaderAt(era.TypeTotalDifficulty, off); it.err != nil { + it.clear() + return true + } + it.next += 1 + return true +} + +// Number returns the current number block the iterator will return. +func (it *RawIterator) Number() uint64 { + return it.next - 1 +} + +// Error returns the error status of the iterator. It should be called before +// reading from any of the iterator's values. +func (it *RawIterator) Error() error { + if it.err == io.EOF { + return nil + } + return it.err +} + +// clear sets all the outputs to nil. +func (it *RawIterator) clear() { + it.Header = nil + it.Body = nil + it.Receipts = nil + it.TotalDifficulty = nil +} diff --git a/internal/era/onedb/reader.go b/internal/era/onedb/reader.go new file mode 100644 index 0000000000..9c66642b8b --- /dev/null +++ b/internal/era/onedb/reader.go @@ -0,0 +1,308 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package onedb + +import ( + "encoding/binary" + "fmt" + "io" + "math/big" + "os" + "path" + "slices" + "strconv" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/internal/era/e2store" + "github.com/ethereum/go-ethereum/rlp" + "github.com/golang/snappy" +) + +// Filename returns a recognizable Era1-formatted file name for the specified +// epoch and network. +func Filename(network string, epoch int, root common.Hash) string { + return fmt.Sprintf("%s-%05d-%s.era1", network, epoch, root.Hex()[2:10]) +} + +// ReadDir reads all the era1 files in a directory for a given network. +// Format: --.era1 +func ReadDir(dir, network string) ([]string, error) { + entries, err := os.ReadDir(dir) + if err != nil { + return nil, fmt.Errorf("error reading directory %s: %w", dir, err) + } + var ( + next = uint64(0) + eras []string + ) + for _, entry := range entries { + if path.Ext(entry.Name()) != ".era1" { + continue + } + parts := strings.Split(entry.Name(), "-") + if len(parts) != 3 || parts[0] != network { + // Invalid era1 filename, skip. + continue + } + if epoch, err := strconv.ParseUint(parts[1], 10, 64); err != nil { + return nil, fmt.Errorf("malformed era1 filename: %s", entry.Name()) + } else if epoch != next { + return nil, fmt.Errorf("missing epoch %d", next) + } + next += 1 + eras = append(eras, entry.Name()) + } + return eras, nil +} + +type ReadAtSeekCloser interface { + io.ReaderAt + io.Seeker + io.Closer +} + +// Era reads and Era1 file. +type Era struct { + 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 { + return nil, err + } + return &Era{ + f: f, + s: e2store.NewReader(f), + m: m, + mu: new(sync.Mutex), + }, nil +} + +// Open returns an Era backed by the given filename. +func Open(filename string) (*Era, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + return From(f) +} + +func (e *Era) Close() error { + return e.f.Close() +} + +// GetBlockByNumber returns the block for the given block number. +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: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count) + } + off, err := e.readOffset(num) + if err != nil { + return nil, err + } + r, n, err := newSnappyReader(e.s, era.TypeCompressedHeader, off) + if err != nil { + return nil, err + } + var header types.Header + if err := rlp.Decode(r, &header); err != nil { + return nil, err + } + off += n + r, _, err = newSnappyReader(e.s, era.TypeCompressedBody, off) + if err != nil { + return nil, err + } + var body types.Body + if err := rlp.Decode(r, &body); err != nil { + return nil, err + } + return types.NewBlockWithHeader(&header).WithBody(body), nil +} + +// GetRawBodyByNumber returns the RLP-encoded body for the given block number. +func (e *Era) GetRawBodyByNumber(num uint64) ([]byte, error) { + if e.m.start > num || e.m.start+e.m.count <= num { + return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count) + } + off, err := e.readOffset(num) + if err != nil { + return nil, err + } + off, err = e.s.SkipN(off, 1) + if err != nil { + return nil, err + } + r, _, err := newSnappyReader(e.s, era.TypeCompressedBody, off) + if err != nil { + return nil, err + } + return io.ReadAll(r) +} + +// GetRawReceiptsByNumber returns the RLP-encoded receipts for the given block number. +func (e *Era) GetRawReceiptsByNumber(num uint64) ([]byte, error) { + if e.m.start > num || e.m.start+e.m.count <= num { + return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count) + } + off, err := e.readOffset(num) + if err != nil { + return nil, err + } + + // Skip over header and body. + off, err = e.s.SkipN(off, 2) + if err != nil { + return nil, err + } + + r, _, err := newSnappyReader(e.s, era.TypeCompressedReceipts, off) + if err != nil { + return nil, err + } + return io.ReadAll(r) +} + +// Accumulator reads the accumulator entry in the Era1 file. +func (e *Era) Accumulator() (common.Hash, error) { + entry, err := e.s.Find(era.TypeAccumulator) + if err != nil { + return common.Hash{}, err + } + return common.BytesToHash(entry.Value), nil +} + +// InitialTD returns initial total difficulty before the difficulty of the +// first block of the Era1 is applied. +func (e *Era) InitialTD() (*big.Int, error) { + var ( + r io.Reader + header types.Header + rawTd []byte + n int64 + off int64 + err error + ) + + // Read first header. + if off, err = e.readOffset(e.m.start); err != nil { + return nil, err + } + if r, n, err = newSnappyReader(e.s, era.TypeCompressedHeader, off); err != nil { + return nil, err + } + if err := rlp.Decode(r, &header); err != nil { + return nil, err + } + off += n + + // Skip over header and body. + off, err = e.s.SkipN(off, 2) + if err != nil { + return nil, err + } + + // Read total difficulty after first block. + if r, _, err = e.s.ReaderAt(era.TypeTotalDifficulty, off); err != nil { + return nil, err + } + rawTd, err = io.ReadAll(r) + if err != nil { + return nil, err + } + slices.Reverse(rawTd) + td := new(big.Int).SetBytes(rawTd) + return td.Sub(td, header.Difficulty), nil +} + +// Start returns the listed start block. +func (e *Era) Start() uint64 { + return e.m.start +} + +// Count returns the total number of blocks in the Era1. +func (e *Era) Count() uint64 { + 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 ( + blockIndexRecordOffset = e.m.length - 24 - int64(e.m.count)*8 // skips start, count, and header + firstIndex = blockIndexRecordOffset + 16 // first index after header / start-num + indexOffset = int64(n-e.m.start) * 8 // desired index * size of indexes + offOffset = firstIndex + indexOffset // offset of block offset + ) + e.mu.Lock() + defer e.mu.Unlock() + clear(e.buf[:]) + if _, err := e.f.ReadAt(e.buf[:], offOffset); err != nil { + return 0, err + } + // Since the block offset is relative from the start of the block index record + // we need to add the record offset to it's offset to get the block's absolute + // offset. + return blockIndexRecordOffset + int64(binary.LittleEndian.Uint64(e.buf[:])), nil +} + +// newSnappyReader returns a snappy.Reader for the e2store entry value at off. +func newSnappyReader(e *e2store.Reader, expectedType uint16, off int64) (io.Reader, int64, error) { + r, n, err := e.ReaderAt(expectedType, off) + if err != nil { + return nil, 0, err + } + return snappy.NewReader(r), int64(n), err +} + +// metadata wraps the metadata in the block index. +type metadata struct { + start uint64 + count uint64 + length int64 +} + +// readMetadata reads the metadata stored in an Era1 file's block index. +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) + // Read count. It's the last 8 bytes of the file. + if _, err = f.ReadAt(b[:8], m.length-8); err != nil { + return + } + m.count = binary.LittleEndian.Uint64(b) + // Read start. It's at the offset -sizeof(m.count) - + // count*sizeof(indexEntry) - sizeof(m.start) + if _, err = f.ReadAt(b[8:], m.length-16-int64(m.count*8)); err != nil { + return + } + m.start = binary.LittleEndian.Uint64(b[8:]) + return +} diff --git a/internal/era2/proof.go b/internal/era/proof.go similarity index 99% rename from internal/era2/proof.go rename to internal/era/proof.go index 3323108d69..667379bb05 100644 --- a/internal/era2/proof.go +++ b/internal/era/proof.go @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package era2 +package era import ( "io" diff --git a/internal/era2/accumulator.go b/internal/era2/accumulator.go deleted file mode 100644 index 732931fa96..0000000000 --- a/internal/era2/accumulator.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2025 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package era2 - -import ( - "errors" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - ssz "github.com/ferranbt/fastssz" -) - -// ComputeAccumulator calculates the SSZ hash tree root of the Era1 -// accumulator of header records. -func ComputeAccumulator(hashes []common.Hash, tds []*big.Int) (common.Hash, error) { - if len(hashes) != len(tds) { - return common.Hash{}, errors.New("must have equal number hashes as td values") - } - if len(hashes) > MaxEraESize { - return common.Hash{}, fmt.Errorf("too many records: have %d, max %d", len(hashes), MaxEraESize) - } - hh := ssz.NewHasher() - for i := range hashes { - rec := headerRecord{hashes[i], tds[i]} - root, err := rec.HashTreeRoot() - if err != nil { - return common.Hash{}, err - } - hh.Append(root[:]) - } - hh.MerkleizeWithMixin(0, uint64(len(hashes)), uint64(MaxEraESize)) - return hh.HashRoot() -} - -// headerRecord is an individual record for a historical header. -// -// See https://github.com/ethereum/portal-network-specs/blob/master/history/history-network.md#the-historical-hashes-accumulator -// for more information. -type headerRecord struct { - Hash common.Hash - TotalDifficulty *big.Int -} - -// GetTree completes the ssz.HashRoot interface, but is unused. -func (h *headerRecord) GetTree() (*ssz.Node, error) { - return nil, nil -} - -// HashTreeRoot ssz hashes the headerRecord object. -func (h *headerRecord) HashTreeRoot() ([32]byte, error) { - return ssz.HashWithDefaultHasher(h) -} - -// HashTreeRootWith ssz hashes the headerRecord object with a hasher. -func (h *headerRecord) HashTreeRootWith(hh ssz.HashWalker) (err error) { - hh.PutBytes(h.Hash[:]) - td := bigToBytes32(h.TotalDifficulty) - hh.PutBytes(td[:]) - hh.Merkleize(0) - return -} - -// bigToBytes32 converts a big.Int into a little-endian 32-byte array. -func bigToBytes32(n *big.Int) (b [32]byte) { - n.FillBytes(b[:]) - reverseOrder(b[:]) - return -} - -// reverseOrder reverses the byte order of a slice. -func reverseOrder(b []byte) []byte { - for i := 0; i < 16; i++ { - b[i], b[32-i-1] = b[32-i-1], b[i] - } - return b -}