mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
internal/era: refactor era to use ReadAt interface
This commit is contained in:
parent
a9304e8c04
commit
545f0b1aa7
2 changed files with 340 additions and 271 deletions
227
internal/era/builder.go
Normal file
227
internal/era/builder.go
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Builder is used to create Era1 archives of block data.
|
||||
//
|
||||
// Era1 files are themselves e2store files. For more information on this format,
|
||||
// see https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md.
|
||||
//
|
||||
// The overall structure of an Era1 file follows closely the structure of an Era file
|
||||
// which contains consensus Layer data (and as a byproduct, EL data after the merge).
|
||||
//
|
||||
// The structure can be summarized through this definition:
|
||||
//
|
||||
// era1 := Version | block-tuple* | other-entries* | Accumulator | BlockIndex
|
||||
// block-tuple := CompressedHeader | CompressedBody | CompressedReceipts | TotalDifficulty
|
||||
//
|
||||
// Each basic element is its own entry:
|
||||
//
|
||||
// Version = { type: [0x65, 0x32], data: nil }
|
||||
// CompressedHeader = { type: [0x03, 0x00], data: snappyFramed(rlp(header)) }
|
||||
// CompressedBody = { type: [0x04, 0x00], data: snappyFramed(rlp(body)) }
|
||||
// CompressedReceipts = { type: [0x05, 0x00], data: snappyFramed(rlp(receipts)) }
|
||||
// TotalDifficulty = { type: [0x06, 0x00], data: uint256(header.total_difficulty) }
|
||||
// Accumulator = { type: [0x07, 0x00], data: accumulator-root }
|
||||
// BlockIndex = { type: [0x32, 0x66], data: block-index }
|
||||
//
|
||||
// Accumulator is computed by constructing an SSZ list of header-records of length at most
|
||||
// 8192 and then calculating the hash_tree_root of that list.
|
||||
//
|
||||
// header-record := { block-hash: Bytes32, total-difficulty: Uint256 }
|
||||
// accumulator := hash_tree_root([]header-record, 8192)
|
||||
//
|
||||
// BlockIndex stores relative offsets to each compressed block entry. The
|
||||
// format is:
|
||||
//
|
||||
// block-index := starting-number | index | index | index ... | count
|
||||
//
|
||||
// starting-number is the first block number in the archive. Every index is a
|
||||
// defined relative to index's location in the file. The total number of block
|
||||
// entries in the file is recorded in count.
|
||||
//
|
||||
// Due to the accumulator size limit of 8192, the maximum number of blocks in
|
||||
// an Era1 batch is also 8192.
|
||||
type Builder struct {
|
||||
w *e2store.Writer
|
||||
startNum *uint64
|
||||
startTd *big.Int
|
||||
indexes []uint64
|
||||
hashes []common.Hash
|
||||
tds []*big.Int
|
||||
written int
|
||||
|
||||
buf *bytes.Buffer
|
||||
snappy *snappy.Writer
|
||||
}
|
||||
|
||||
// NewBuilder returns a new Builder instance.
|
||||
func NewBuilder(w io.Writer) *Builder {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
return &Builder{
|
||||
w: e2store.NewWriter(w),
|
||||
buf: buf,
|
||||
snappy: snappy.NewBufferedWriter(buf),
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
eh, err := rlp.EncodeToBytes(block.Header())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eb, err := rlp.EncodeToBytes(block.Body())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
er, err := rlp.EncodeToBytes(receipts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.AddRLP(eh, eb, er, block.NumberU64(), block.Hash(), td, block.Difficulty())
|
||||
}
|
||||
|
||||
// AddRLP writes a compressed block entry and compressed receipts entry to the
|
||||
// underlying e2store file.
|
||||
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 {
|
||||
if err := writeVersion(b.w); err != nil {
|
||||
return err
|
||||
}
|
||||
n := number
|
||||
b.startNum = &n
|
||||
b.startTd = new(big.Int).Sub(td, difficulty)
|
||||
}
|
||||
if len(b.indexes) >= MaxEra1Size {
|
||||
return fmt.Errorf("exceeds maximum batch size of %d", MaxEra1Size)
|
||||
}
|
||||
|
||||
b.indexes = append(b.indexes, uint64(b.written))
|
||||
b.hashes = append(b.hashes, hash)
|
||||
b.tds = append(b.tds, td)
|
||||
|
||||
// Small helper to take care snappy encoding and writing e2store entry.
|
||||
snappyWrite := func(typ uint16, in []byte) error {
|
||||
var (
|
||||
buf = b.buf
|
||||
s = b.snappy
|
||||
)
|
||||
buf.Reset()
|
||||
s.Reset(buf)
|
||||
if _, err := b.snappy.Write(in); err != nil {
|
||||
return fmt.Errorf("error snappy encoding: %w", err)
|
||||
}
|
||||
if err := s.Flush(); err != nil {
|
||||
return fmt.Errorf("error flushing snappy encoding: %w", err)
|
||||
}
|
||||
n, err := b.w.Write(typ, b.buf.Bytes())
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing e2store entry: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write block data.
|
||||
if err := snappyWrite(TypeCompressedHeader, header); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := snappyWrite(TypeCompressedBody, body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := snappyWrite(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[:])
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize computes the accumulator and block index values, then writes the
|
||||
// corresponding e2store entries.
|
||||
func (b *Builder) Finalize() (common.Hash, error) {
|
||||
if b.startNum == nil {
|
||||
return common.Hash{}, fmt.Errorf("finalize called on empty builder")
|
||||
}
|
||||
// Compute accumulator root and write entry.
|
||||
root, err := 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[:])
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return common.Hash{}, fmt.Errorf("error writing accumulator: %w", err)
|
||||
}
|
||||
// Get beginning of index entry to calculate block relative offset.
|
||||
base := int64(b.written + (3 * 8)) // skip e2store header (type, length) and start block
|
||||
|
||||
// Construct block index. Detailed format described in Builder
|
||||
// documentation, but it is essentially encoded as:
|
||||
// "start | index | index | ... | count"
|
||||
var (
|
||||
count = len(b.indexes)
|
||||
index = make([]byte, 16+count*8)
|
||||
)
|
||||
binary.LittleEndian.PutUint64(index, *b.startNum)
|
||||
// Each offset is relative from the position it is encoded in the
|
||||
// index. This means that even if the same block was to be included in
|
||||
// the index twice (this would be invalid anyways), the relative offset
|
||||
// would be different. The idea with this is that after reading a
|
||||
// relative offset, the corresponding block can be quickly read by
|
||||
// performing a seek relative to the current position.
|
||||
for i, offset := range b.indexes {
|
||||
relative := int64(offset) - (base + int64(i)*8)
|
||||
binary.LittleEndian.PutUint64(index[8+i*8:], uint64(relative))
|
||||
}
|
||||
binary.LittleEndian.PutUint64(index[8+count*8:], uint64(count))
|
||||
|
||||
// Finally, write the block index entry.
|
||||
if _, err := b.w.Write(TypeBlockIndex, index); err != nil {
|
||||
return common.Hash{}, fmt.Errorf("unable to write block index: %w", err)
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// writeVersion writes a version entry to e2store.
|
||||
func writeVersion(w *e2store.Writer) error {
|
||||
_, err := w.Write(TypeVersion, nil)
|
||||
return err
|
||||
}
|
||||
|
|
@ -83,323 +83,176 @@ func ReadDir(dir, network string) ([]string, error) {
|
|||
return eras, nil
|
||||
}
|
||||
|
||||
// Builder is used to create Era1 archives of block data.
|
||||
//
|
||||
// Era1 files are themselves e2store files. For more information on this format,
|
||||
// see https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md.
|
||||
//
|
||||
// The overall structure of an Era1 file follows closely the structure of an Era file
|
||||
// which contains consensus Layer data (and as a byproduct, EL data after the merge).
|
||||
//
|
||||
// The structure can be summarized through this definition:
|
||||
//
|
||||
// era1 := Version | block-tuple* | other-entries* | Accumulator | BlockIndex
|
||||
// block-tuple := CompressedHeader | CompressedBody | CompressedReceipts | TotalDifficulty
|
||||
//
|
||||
// Each basic element is its own entry:
|
||||
//
|
||||
// Version = { type: [0x65, 0x32], data: nil }
|
||||
// CompressedHeader = { type: [0x03, 0x00], data: snappyFramed(rlp(header)) }
|
||||
// CompressedBody = { type: [0x04, 0x00], data: snappyFramed(rlp(body)) }
|
||||
// CompressedReceipts = { type: [0x05, 0x00], data: snappyFramed(rlp(receipts)) }
|
||||
// TotalDifficulty = { type: [0x06, 0x00], data: uint256(header.total_difficulty) }
|
||||
// Accumulator = { type: [0x07, 0x00], data: accumulator-root }
|
||||
// BlockIndex = { type: [0x32, 0x66], data: block-index }
|
||||
//
|
||||
// Accumulator is computed by constructing an SSZ list of header-records of length at most
|
||||
// 8192 and then calculating the hash_tree_root of that list.
|
||||
//
|
||||
// header-record := { block-hash: Bytes32, total-difficulty: Uint256 }
|
||||
// accumulator := hash_tree_root([]header-record, 8192)
|
||||
//
|
||||
// BlockIndex stores relative offsets to each compressed block entry. The
|
||||
// format is:
|
||||
//
|
||||
// block-index := starting-number | index | index | index ... | count
|
||||
//
|
||||
// starting-number is the first block number in the archive. Every index is a
|
||||
// defined relative to index's location in the file. The total number of block
|
||||
// entries in the file is recorded in count.
|
||||
//
|
||||
// Due to the accumulator size limit of 8192, the maximum number of blocks in
|
||||
// an Era1 batch is also 8192.
|
||||
type Builder struct {
|
||||
w *e2store.Writer
|
||||
startNum *uint64
|
||||
startTd *big.Int
|
||||
indexes []uint64
|
||||
hashes []common.Hash
|
||||
tds []*big.Int
|
||||
written int
|
||||
}
|
||||
|
||||
// NewBuilder returns a new Builder instance.
|
||||
func NewBuilder(w io.Writer) *Builder {
|
||||
return &Builder{
|
||||
w: e2store.NewWriter(w),
|
||||
hashes: make([]common.Hash, 0),
|
||||
tds: make([]*big.Int, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
eh, err := rlp.EncodeToBytes(block.Header())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eb, err := rlp.EncodeToBytes(block.Body())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
er, err := rlp.EncodeToBytes(receipts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.AddRLP(eh, eb, er, block.NumberU64(), block.Hash(), td, block.Difficulty())
|
||||
}
|
||||
|
||||
// AddRLP writes a compressed block entry and compressed receipts entry to the
|
||||
// underlying e2store file.
|
||||
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 {
|
||||
if err := writeVersion(b.w); err != nil {
|
||||
return err
|
||||
}
|
||||
n := number
|
||||
b.startNum = &n
|
||||
b.startTd = new(big.Int).Sub(td, difficulty)
|
||||
}
|
||||
if len(b.indexes) >= MaxEra1Size {
|
||||
return fmt.Errorf("exceeds maximum batch size of %d", MaxEra1Size)
|
||||
}
|
||||
|
||||
b.indexes = append(b.indexes, uint64(b.written))
|
||||
b.hashes = append(b.hashes, hash)
|
||||
b.tds = append(b.tds, td)
|
||||
|
||||
// Small helper to take care snappy encoding and writing e2store entry.
|
||||
snappyWrite := func(typ uint16, in []byte) error {
|
||||
var (
|
||||
buf = bytes.NewBuffer(nil)
|
||||
s = snappy.NewBufferedWriter(buf)
|
||||
)
|
||||
if _, err := s.Write(in); err != nil {
|
||||
return fmt.Errorf("error snappy encoding: %w", err)
|
||||
}
|
||||
if err := s.Flush(); err != nil {
|
||||
return fmt.Errorf("error flushing snappy encoding: %w", err)
|
||||
}
|
||||
n, err := b.w.Write(typ, buf.Bytes())
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing e2store entry: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write block data.
|
||||
if err := snappyWrite(TypeCompressedHeader, header); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := snappyWrite(TypeCompressedBody, body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := snappyWrite(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[:])
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize computes the accumulator and block index values, then writes the
|
||||
// corresponding e2store entries.
|
||||
func (b *Builder) Finalize() (common.Hash, error) {
|
||||
if b.startNum == nil {
|
||||
return common.Hash{}, fmt.Errorf("finalize called on empty builder")
|
||||
}
|
||||
// Compute accumulator root and write entry.
|
||||
root, err := 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[:])
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return common.Hash{}, fmt.Errorf("error writing accumulator: %w", err)
|
||||
}
|
||||
// Get beginning of index entry to calculate block relative offset.
|
||||
base := int64(b.written + (3 * 8)) // skip e2store header (type, length) and start block
|
||||
|
||||
// Construct block index. Detailed format described in Builder
|
||||
// documentation, but it is essentially encoded as:
|
||||
// "start | index | index | ... | count"
|
||||
var (
|
||||
count = len(b.indexes)
|
||||
index = make([]byte, 16+count*8)
|
||||
)
|
||||
binary.LittleEndian.PutUint64(index, *b.startNum)
|
||||
// Each offset is relative from the position it is encoded in the
|
||||
// index. This means that even if the same block was to be included in
|
||||
// the index twice (this would be invalid anyways), the relative offset
|
||||
// would be different. The idea with this is that after reading a
|
||||
// relative offset, the corresponding block can be quickly read by
|
||||
// performing a seek relative to the current position.
|
||||
for i, offset := range b.indexes {
|
||||
relative := int64(offset) - (base + int64(i)*8)
|
||||
binary.LittleEndian.PutUint64(index[8+i*8:], uint64(relative))
|
||||
}
|
||||
binary.LittleEndian.PutUint64(index[8+count*8:], uint64(count))
|
||||
|
||||
// Finally, write the block index entry.
|
||||
if _, err := b.w.Write(TypeBlockIndex, index); err != nil {
|
||||
return common.Hash{}, fmt.Errorf("unable to write block index: %w", err)
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// writeVersion writes a version entry to e2store.
|
||||
func writeVersion(w *e2store.Writer) error {
|
||||
_, err := w.Write(TypeVersion, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Reader reads an Era1 archive.
|
||||
// See Builder documentation for a detailed explanation of the Era1 format.
|
||||
type Reader struct {
|
||||
r io.ReadSeeker
|
||||
offset uint64
|
||||
metadata metadata
|
||||
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.Seeker
|
||||
}
|
||||
|
||||
// NewReader returns a new Reader instance.
|
||||
func NewReader(r io.ReadSeeker) (*Reader, error) {
|
||||
m, err := readMetadata(r)
|
||||
func NewReader(r ReadAtSeeker) (*Reader, error) {
|
||||
length, err := r.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Reader{r, m.start, m}, nil
|
||||
m, err := readMetadata(r, length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Reader{
|
||||
r: r,
|
||||
e: e2store.NewReader(r),
|
||||
buf: [8]byte{},
|
||||
next: m.start,
|
||||
length: length,
|
||||
metadata: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// readOffset reads a specific block's offset from the block index. The value n
|
||||
// is the absolute block number desired. It is normalized against the index's
|
||||
// start block.
|
||||
// is the absolute block number desired.
|
||||
func (r *Reader) readOffset(n uint64) (int64, error) {
|
||||
// Seek to the encoding of the block's offset.
|
||||
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
|
||||
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
|
||||
)
|
||||
if _, err := r.r.Seek(firstIndex+indexOffset, io.SeekEnd); err != nil {
|
||||
r.clearBuffer()
|
||||
if _, err := r.r.ReadAt(r.buf[:], offOffset); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Read the block's offset.
|
||||
var offset int64
|
||||
if err := binary.Read(r.r, binary.LittleEndian, &offset); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return offset, nil
|
||||
// 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.offset)
|
||||
block, receipts, err := r.ReadBlockAndReceipts(r.next)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r.offset += 1
|
||||
r.next += 1
|
||||
return block, receipts, nil
|
||||
}
|
||||
|
||||
// ReadHeader reads the header number n RLP.
|
||||
func (r *Reader) ReadHeaderRLP(n uint64) ([]byte, error) {
|
||||
// Determine if the request can served by current the Era1 file, e.g. n
|
||||
// must be within the range of blocks specified in the block index
|
||||
// metadata.
|
||||
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.
|
||||
offset, err := r.readOffset(n)
|
||||
off, err := r.readOffset(n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading block offset: %w", err)
|
||||
}
|
||||
if _, err := r.r.Seek(offset, io.SeekCurrent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read header.
|
||||
entry, err := e2store.NewReader(r.r).Read()
|
||||
if err != nil {
|
||||
var e e2store.Entry
|
||||
if _, err := r.e.ReadAt(&e, off); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry.Type != TypeCompressedHeader {
|
||||
return nil, fmt.Errorf("expected header entry, got %x", entry.Type)
|
||||
if e.Type != TypeCompressedHeader {
|
||||
return nil, fmt.Errorf("expected header entry, got %x", e.Type)
|
||||
}
|
||||
return io.ReadAll(snappy.NewReader(bytes.NewReader(entry.Value)))
|
||||
return io.ReadAll(snappy.NewReader(bytes.NewReader(e.Value)))
|
||||
}
|
||||
|
||||
func skipN(r *Reader, off int64, n int) (int64, error) {
|
||||
for i := 0; i < n; i++ {
|
||||
_, length, err := r.e.ReadMetadataAt(off) // header
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off += e2store.HeaderSize + int64(length)
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
// ReadBodyRLP reads the block body number n RLP.
|
||||
func (r *Reader) ReadBodyRLP(n uint64) ([]byte, error) {
|
||||
// Orient cursor.
|
||||
_, err := r.ReadHeaderRLP(n)
|
||||
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 over header entry to get to body.
|
||||
if off, err = skipN(r, off, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read body.
|
||||
entry, err := e2store.NewReader(r.r).Read()
|
||||
if err != nil {
|
||||
var e e2store.Entry
|
||||
if _, err := r.e.ReadAt(&e, off); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry.Type != TypeCompressedBody {
|
||||
return nil, fmt.Errorf("expected body entry, got %x", entry.Type)
|
||||
if e.Type != TypeCompressedBody {
|
||||
return nil, fmt.Errorf("expected body entry, got %x", e.Type)
|
||||
}
|
||||
return io.ReadAll(snappy.NewReader(bytes.NewReader(entry.Value)))
|
||||
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) {
|
||||
// Orient cursor.
|
||||
_, err := r.ReadBodyRLP(n)
|
||||
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 over header entry to get to body.
|
||||
if off, err = skipN(r, off, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read receipts.
|
||||
entry, err := e2store.NewReader(r.r).Read()
|
||||
if err != nil {
|
||||
var e e2store.Entry
|
||||
if _, err := r.e.ReadAt(&e, off); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry.Type != TypeCompressedReceipts {
|
||||
return nil, fmt.Errorf("expected receipts entry, got %x", entry.Type)
|
||||
if e.Type != TypeCompressedReceipts {
|
||||
return nil, fmt.Errorf("expected receipts entry, got %x", e.Type)
|
||||
}
|
||||
return io.ReadAll(snappy.NewReader(bytes.NewReader(entry.Value)))
|
||||
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) {
|
||||
// Orient cursor.
|
||||
_, err := r.ReadReceiptsRLP(n)
|
||||
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 over header entry to get to body.
|
||||
if off, err = skipN(r, off, 3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read totaly difficulty.
|
||||
entry, err := e2store.NewReader(r.r).Read()
|
||||
if err != nil {
|
||||
// Read receipts.
|
||||
var e e2store.Entry
|
||||
if _, err := r.e.ReadAt(&e, off); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry.Type != TypeTotalDifficulty {
|
||||
return nil, fmt.Errorf("expected total difficulty entry, got %x", entry.Type)
|
||||
if e.Type != TypeTotalDifficulty {
|
||||
return nil, fmt.Errorf("expected receipts entry, got %x", e.Type)
|
||||
}
|
||||
return new(big.Int).SetBytes(reverseOrder(entry.Value)), nil
|
||||
return new(big.Int).SetBytes(reverseOrder(e.Value)), nil
|
||||
}
|
||||
|
||||
// ReadHeader reads the header number n.
|
||||
|
|
@ -454,11 +307,7 @@ func (r *Reader) ReadBlockAndReceipts(n uint64) (*types.Block, types.Receipts, e
|
|||
|
||||
// Accumulator reads the accumulator entry in the Era1 file.
|
||||
func (r *Reader) Accumulator() (common.Hash, error) {
|
||||
_, err := r.seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
entry, err := e2store.NewReader(r.r).Find(TypeAccumulator)
|
||||
entry, err := r.e.Find(TypeAccumulator)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
|
@ -468,16 +317,12 @@ func (r *Reader) Accumulator() (common.Hash, error) {
|
|||
// InitialTD returns initial total difficulty before the difficulty of the
|
||||
// first block of the Era1 is applied.
|
||||
func (r *Reader) InitialTD() (*big.Int, error) {
|
||||
_, err := r.seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := r.ReadHeader(r.Start())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Above seek also sets reader so next TD entry will be for this block.
|
||||
entry, err := e2store.NewReader(r.r).Find(TypeTotalDifficulty)
|
||||
entry, err := r.e.Find(TypeTotalDifficulty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -495,9 +340,11 @@ func (r *Reader) Count() uint64 {
|
|||
return r.metadata.count
|
||||
}
|
||||
|
||||
// seek is a shorthand method for calling seek on the inner reader.
|
||||
func (r *Reader) seek(offset int64, whence int) (int64, error) {
|
||||
return r.r.Seek(offset, whence)
|
||||
// clearBuffer zeroes out the buffer.
|
||||
func (r *Reader) clearBuffer() {
|
||||
for i := 0; i < len(r.buf); i++ {
|
||||
r.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// metadata wraps the metadata in the block index.
|
||||
|
|
@ -506,23 +353,18 @@ type metadata struct {
|
|||
}
|
||||
|
||||
// readMetadata reads the metadata stored in an Era1 file's block index.
|
||||
func readMetadata(r io.ReadSeeker) (m metadata, err error) {
|
||||
// Seek to count value. It's the last 8 bytes of the file.
|
||||
if _, err = r.Seek(-8, io.SeekEnd); err != nil {
|
||||
func readMetadata(r io.ReaderAt, length int64) (m metadata, err error) {
|
||||
b := make([]byte, 16)
|
||||
// Read count. It's the last 8 bytes of the file.
|
||||
if _, err = r.ReadAt(b[:8], length-8); err != nil {
|
||||
return
|
||||
}
|
||||
// Read count.
|
||||
if err = binary.Read(r, binary.LittleEndian, &m.count); err != nil {
|
||||
return
|
||||
}
|
||||
// Seek to start value. It's at the offset -sizeof(m.count) -
|
||||
m.count = binary.LittleEndian.Uint64(b)
|
||||
// Read start. It's at the offset -sizeof(m.count) -
|
||||
// count*sizeof(indexEntry) - sizeof(m.start)
|
||||
if _, err = r.Seek(-16-int64(m.count)*8, io.SeekEnd); err != nil {
|
||||
return
|
||||
}
|
||||
// Read start.
|
||||
if err = binary.Read(r, binary.LittleEndian, &m.start); err != nil {
|
||||
if _, err = r.ReadAt(b[8:], length-16-int64(m.count*8)); err != nil {
|
||||
return
|
||||
}
|
||||
m.start = binary.LittleEndian.Uint64(b[8:])
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue