github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/internal/era/era.go (about)

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