github.com/ethereum/go-ethereum@v1.16.1/internal/era/era.go (about)

     1  // Copyright 2024 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package era
    18  
    19  import (
    20  	"encoding/binary"
    21  	"fmt"
    22  	"io"
    23  	"math/big"
    24  	"os"
    25  	"path"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  
    30  	"github.com/ethereum/go-ethereum/common"
    31  	"github.com/ethereum/go-ethereum/core/types"
    32  	"github.com/ethereum/go-ethereum/internal/era/e2store"
    33  	"github.com/ethereum/go-ethereum/rlp"
    34  	"github.com/golang/snappy"
    35  )
    36  
    37  var (
    38  	TypeVersion            uint16 = 0x3265
    39  	TypeCompressedHeader   uint16 = 0x03
    40  	TypeCompressedBody     uint16 = 0x04
    41  	TypeCompressedReceipts uint16 = 0x05
    42  	TypeTotalDifficulty    uint16 = 0x06
    43  	TypeAccumulator        uint16 = 0x07
    44  	TypeBlockIndex         uint16 = 0x3266
    45  
    46  	MaxEra1Size = 8192
    47  )
    48  
    49  // Filename returns a recognizable Era1-formatted file name for the specified
    50  // epoch and network.
    51  func Filename(network string, epoch int, root common.Hash) string {
    52  	return fmt.Sprintf("%s-%05d-%s.era1", network, epoch, root.Hex()[2:10])
    53  }
    54  
    55  // ReadDir reads all the era1 files in a directory for a given network.
    56  // Format: <network>-<epoch>-<hexroot>.era1
    57  func ReadDir(dir, network string) ([]string, error) {
    58  	entries, err := os.ReadDir(dir)
    59  	if err != nil {
    60  		return nil, fmt.Errorf("error reading directory %s: %w", dir, err)
    61  	}
    62  	var (
    63  		next = uint64(0)
    64  		eras []string
    65  	)
    66  	for _, entry := range entries {
    67  		if path.Ext(entry.Name()) != ".era1" {
    68  			continue
    69  		}
    70  		parts := strings.Split(entry.Name(), "-")
    71  		if len(parts) != 3 || parts[0] != network {
    72  			// Invalid era1 filename, skip.
    73  			continue
    74  		}
    75  		if epoch, err := strconv.ParseUint(parts[1], 10, 64); err != nil {
    76  			return nil, fmt.Errorf("malformed era1 filename: %s", entry.Name())
    77  		} else if epoch != next {
    78  			return nil, fmt.Errorf("missing epoch %d", next)
    79  		}
    80  		next += 1
    81  		eras = append(eras, entry.Name())
    82  	}
    83  	return eras, nil
    84  }
    85  
    86  type ReadAtSeekCloser interface {
    87  	io.ReaderAt
    88  	io.Seeker
    89  	io.Closer
    90  }
    91  
    92  // Era reads and Era1 file.
    93  type Era struct {
    94  	f   ReadAtSeekCloser // backing era1 file
    95  	s   *e2store.Reader  // e2store reader over f
    96  	m   metadata         // start, count, length info
    97  	mu  *sync.Mutex      // lock for buf
    98  	buf [8]byte          // buffer reading entry offsets
    99  }
   100  
   101  // From returns an Era backed by f.
   102  func From(f ReadAtSeekCloser) (*Era, error) {
   103  	m, err := readMetadata(f)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	return &Era{
   108  		f:  f,
   109  		s:  e2store.NewReader(f),
   110  		m:  m,
   111  		mu: new(sync.Mutex),
   112  	}, nil
   113  }
   114  
   115  // Open returns an Era backed by the given filename.
   116  func Open(filename string) (*Era, error) {
   117  	f, err := os.Open(filename)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	return From(f)
   122  }
   123  
   124  func (e *Era) Close() error {
   125  	return e.f.Close()
   126  }
   127  
   128  // GetBlockByNumber returns the block for the given block number.
   129  func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
   130  	if e.m.start > num || e.m.start+e.m.count <= num {
   131  		return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
   132  	}
   133  	off, err := e.readOffset(num)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	r, n, err := newSnappyReader(e.s, TypeCompressedHeader, off)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	var header types.Header
   142  	if err := rlp.Decode(r, &header); err != nil {
   143  		return nil, err
   144  	}
   145  	off += n
   146  	r, _, err = newSnappyReader(e.s, TypeCompressedBody, off)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	var body types.Body
   151  	if err := rlp.Decode(r, &body); err != nil {
   152  		return nil, err
   153  	}
   154  	return types.NewBlockWithHeader(&header).WithBody(body), nil
   155  }
   156  
   157  // GetRawBodyByNumber returns the RLP-encoded body for the given block number.
   158  func (e *Era) GetRawBodyByNumber(num uint64) ([]byte, error) {
   159  	if e.m.start > num || e.m.start+e.m.count <= num {
   160  		return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
   161  	}
   162  	off, err := e.readOffset(num)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	off, err = e.s.SkipN(off, 1)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	r, _, err := newSnappyReader(e.s, TypeCompressedBody, off)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	return io.ReadAll(r)
   175  }
   176  
   177  // GetRawReceiptsByNumber returns the RLP-encoded receipts for the given block number.
   178  func (e *Era) GetRawReceiptsByNumber(num uint64) ([]byte, error) {
   179  	if e.m.start > num || e.m.start+e.m.count <= num {
   180  		return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
   181  	}
   182  	off, err := e.readOffset(num)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	// Skip over header and body.
   188  	off, err = e.s.SkipN(off, 2)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	r, _, err := newSnappyReader(e.s, TypeCompressedReceipts, off)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	return io.ReadAll(r)
   198  }
   199  
   200  // Accumulator reads the accumulator entry in the Era1 file.
   201  func (e *Era) Accumulator() (common.Hash, error) {
   202  	entry, err := e.s.Find(TypeAccumulator)
   203  	if err != nil {
   204  		return common.Hash{}, err
   205  	}
   206  	return common.BytesToHash(entry.Value), nil
   207  }
   208  
   209  // InitialTD returns initial total difficulty before the difficulty of the
   210  // first block of the Era1 is applied.
   211  func (e *Era) InitialTD() (*big.Int, error) {
   212  	var (
   213  		r      io.Reader
   214  		header types.Header
   215  		rawTd  []byte
   216  		n      int64
   217  		off    int64
   218  		err    error
   219  	)
   220  
   221  	// Read first header.
   222  	if off, err = e.readOffset(e.m.start); err != nil {
   223  		return nil, err
   224  	}
   225  	if r, n, err = newSnappyReader(e.s, TypeCompressedHeader, off); err != nil {
   226  		return nil, err
   227  	}
   228  	if err := rlp.Decode(r, &header); err != nil {
   229  		return nil, err
   230  	}
   231  	off += n
   232  
   233  	// Skip over header and body.
   234  	off, err = e.s.SkipN(off, 2)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	// Read total difficulty after first block.
   240  	if r, _, err = e.s.ReaderAt(TypeTotalDifficulty, off); err != nil {
   241  		return nil, err
   242  	}
   243  	rawTd, err = io.ReadAll(r)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	td := new(big.Int).SetBytes(reverseOrder(rawTd))
   248  	return td.Sub(td, header.Difficulty), nil
   249  }
   250  
   251  // Start returns the listed start block.
   252  func (e *Era) Start() uint64 {
   253  	return e.m.start
   254  }
   255  
   256  // Count returns the total number of blocks in the Era1.
   257  func (e *Era) Count() uint64 {
   258  	return e.m.count
   259  }
   260  
   261  // readOffset reads a specific block's offset from the block index. The value n
   262  // is the absolute block number desired.
   263  func (e *Era) readOffset(n uint64) (int64, error) {
   264  	var (
   265  		blockIndexRecordOffset = e.m.length - 24 - int64(e.m.count)*8 // skips start, count, and header
   266  		firstIndex             = blockIndexRecordOffset + 16          // first index after header / start-num
   267  		indexOffset            = int64(n-e.m.start) * 8               // desired index * size of indexes
   268  		offOffset              = firstIndex + indexOffset             // offset of block offset
   269  	)
   270  	e.mu.Lock()
   271  	defer e.mu.Unlock()
   272  	clear(e.buf[:])
   273  	if _, err := e.f.ReadAt(e.buf[:], offOffset); err != nil {
   274  		return 0, err
   275  	}
   276  	// Since the block offset is relative from the start of the block index record
   277  	// we need to add the record offset to it's offset to get the block's absolute
   278  	// offset.
   279  	return blockIndexRecordOffset + int64(binary.LittleEndian.Uint64(e.buf[:])), nil
   280  }
   281  
   282  // newSnappyReader returns a snappy.Reader for the e2store entry value at off.
   283  func newSnappyReader(e *e2store.Reader, expectedType uint16, off int64) (io.Reader, int64, error) {
   284  	r, n, err := e.ReaderAt(expectedType, off)
   285  	if err != nil {
   286  		return nil, 0, err
   287  	}
   288  	return snappy.NewReader(r), int64(n), err
   289  }
   290  
   291  // metadata wraps the metadata in the block index.
   292  type metadata struct {
   293  	start  uint64
   294  	count  uint64
   295  	length int64
   296  }
   297  
   298  // readMetadata reads the metadata stored in an Era1 file's block index.
   299  func readMetadata(f ReadAtSeekCloser) (m metadata, err error) {
   300  	// Determine length of reader.
   301  	if m.length, err = f.Seek(0, io.SeekEnd); err != nil {
   302  		return
   303  	}
   304  	b := make([]byte, 16)
   305  	// Read count. It's the last 8 bytes of the file.
   306  	if _, err = f.ReadAt(b[:8], m.length-8); err != nil {
   307  		return
   308  	}
   309  	m.count = binary.LittleEndian.Uint64(b)
   310  	// Read start. It's at the offset -sizeof(m.count) -
   311  	// count*sizeof(indexEntry) - sizeof(m.start)
   312  	if _, err = f.ReadAt(b[8:], m.length-16-int64(m.count*8)); err != nil {
   313  		return
   314  	}
   315  	m.start = binary.LittleEndian.Uint64(b[8:])
   316  	return
   317  }