github.com/ethereum/go-ethereum@v1.14.3/internal/era/e2store/e2store.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 e2store 18 19 import ( 20 "encoding/binary" 21 "errors" 22 "fmt" 23 "io" 24 ) 25 26 const ( 27 headerSize = 8 28 valueSizeLimit = 1024 * 1024 * 50 29 ) 30 31 // Entry is a variable-length-data record in an e2store. 32 type Entry struct { 33 Type uint16 34 Value []byte 35 } 36 37 // Writer writes entries using e2store encoding. 38 // For more information on this format, see: 39 // https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md 40 type Writer struct { 41 w io.Writer 42 } 43 44 // NewWriter returns a new Writer that writes to w. 45 func NewWriter(w io.Writer) *Writer { 46 return &Writer{w} 47 } 48 49 // Write writes a single e2store entry to w. 50 // An entry is encoded in a type-length-value format. The first 8 bytes of the 51 // record store the type (2 bytes), the length (4 bytes), and some reserved 52 // data (2 bytes). The remaining bytes store b. 53 func (w *Writer) Write(typ uint16, b []byte) (int, error) { 54 buf := make([]byte, headerSize) 55 binary.LittleEndian.PutUint16(buf, typ) 56 binary.LittleEndian.PutUint32(buf[2:], uint32(len(b))) 57 58 // Write header. 59 if n, err := w.w.Write(buf); err != nil { 60 return n, err 61 } 62 // Write value, return combined write size. 63 n, err := w.w.Write(b) 64 return n + headerSize, err 65 } 66 67 // A Reader reads entries from an e2store-encoded file. 68 // For more information on this format, see 69 // https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md 70 type Reader struct { 71 r io.ReaderAt 72 offset int64 73 } 74 75 // NewReader returns a new Reader that reads from r. 76 func NewReader(r io.ReaderAt) *Reader { 77 return &Reader{r, 0} 78 } 79 80 // Read reads one Entry from r. 81 func (r *Reader) Read() (*Entry, error) { 82 var e Entry 83 n, err := r.ReadAt(&e, r.offset) 84 if err != nil { 85 return nil, err 86 } 87 r.offset += int64(n) 88 return &e, nil 89 } 90 91 // ReadAt reads one Entry from r at the specified offset. 92 func (r *Reader) ReadAt(entry *Entry, off int64) (int, error) { 93 typ, length, err := r.ReadMetadataAt(off) 94 if err != nil { 95 return 0, err 96 } 97 entry.Type = typ 98 99 // Check length bounds. 100 if length > valueSizeLimit { 101 return headerSize, fmt.Errorf("item larger than item size limit %d: have %d", valueSizeLimit, length) 102 } 103 if length == 0 { 104 return headerSize, nil 105 } 106 107 // Read value. 108 val := make([]byte, length) 109 if n, err := r.r.ReadAt(val, off+headerSize); err != nil { 110 n += headerSize 111 // An entry with a non-zero length should not return EOF when 112 // reading the value. 113 if err == io.EOF { 114 return n, io.ErrUnexpectedEOF 115 } 116 return n, err 117 } 118 entry.Value = val 119 return int(headerSize + length), nil 120 } 121 122 // ReaderAt returns an io.Reader delivering value data for the entry at 123 // the specified offset. If the entry type does not match the expected type, an 124 // error is returned. 125 func (r *Reader) ReaderAt(expectedType uint16, off int64) (io.Reader, int, error) { 126 // problem = need to return length+headerSize not just value length via section reader 127 typ, length, err := r.ReadMetadataAt(off) 128 if err != nil { 129 return nil, headerSize, err 130 } 131 if typ != expectedType { 132 return nil, headerSize, fmt.Errorf("wrong type, want %d have %d", expectedType, typ) 133 } 134 if length > valueSizeLimit { 135 return nil, headerSize, fmt.Errorf("item larger than item size limit %d: have %d", valueSizeLimit, length) 136 } 137 return io.NewSectionReader(r.r, off+headerSize, int64(length)), headerSize + int(length), nil 138 } 139 140 // LengthAt reads the header at off and returns the total length of the entry, 141 // including header. 142 func (r *Reader) LengthAt(off int64) (int64, error) { 143 _, length, err := r.ReadMetadataAt(off) 144 if err != nil { 145 return 0, err 146 } 147 return int64(length) + headerSize, nil 148 } 149 150 // ReadMetadataAt reads the header metadata at the given offset. 151 func (r *Reader) ReadMetadataAt(off int64) (typ uint16, length uint32, err error) { 152 b := make([]byte, headerSize) 153 if n, err := r.r.ReadAt(b, off); err != nil { 154 if err == io.EOF && n > 0 { 155 return 0, 0, io.ErrUnexpectedEOF 156 } 157 return 0, 0, err 158 } 159 typ = binary.LittleEndian.Uint16(b) 160 length = binary.LittleEndian.Uint32(b[2:]) 161 162 // Check reserved bytes of header. 163 if b[6] != 0 || b[7] != 0 { 164 return 0, 0, errors.New("reserved bytes are non-zero") 165 } 166 167 return typ, length, nil 168 } 169 170 // Find returns the first entry with the matching type. 171 func (r *Reader) Find(want uint16) (*Entry, error) { 172 var ( 173 off int64 174 typ uint16 175 length uint32 176 err error 177 ) 178 for { 179 typ, length, err = r.ReadMetadataAt(off) 180 if err == io.EOF { 181 return nil, io.EOF 182 } else if err != nil { 183 return nil, err 184 } 185 if typ == want { 186 var e Entry 187 if _, err := r.ReadAt(&e, off); err != nil { 188 return nil, err 189 } 190 return &e, nil 191 } 192 off += int64(headerSize + length) 193 } 194 } 195 196 // FindAll returns all entries with the matching type. 197 func (r *Reader) FindAll(want uint16) ([]*Entry, error) { 198 var ( 199 off int64 200 typ uint16 201 length uint32 202 entries []*Entry 203 err error 204 ) 205 for { 206 typ, length, err = r.ReadMetadataAt(off) 207 if err == io.EOF { 208 return entries, nil 209 } else if err != nil { 210 return entries, err 211 } 212 if typ == want { 213 e := new(Entry) 214 if _, err := r.ReadAt(e, off); err != nil { 215 return entries, err 216 } 217 entries = append(entries, e) 218 } 219 off += int64(headerSize + length) 220 } 221 }