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 }