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 }