github.com/ethereum/go-ethereum@v1.16.1/internal/era/builder.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 "bytes" 21 "encoding/binary" 22 "errors" 23 "fmt" 24 "io" 25 "math/big" 26 27 "github.com/ethereum/go-ethereum/common" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/internal/era/e2store" 30 "github.com/ethereum/go-ethereum/rlp" 31 "github.com/golang/snappy" 32 ) 33 34 // Builder is used to create Era1 archives of block data. 35 // 36 // Era1 files are themselves e2store files. For more information on this format, 37 // see https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md. 38 // 39 // The overall structure of an Era1 file follows closely the structure of an Era file 40 // which contains consensus Layer data (and as a byproduct, EL data after the merge). 41 // 42 // The structure can be summarized through this definition: 43 // 44 // era1 := Version | block-tuple* | other-entries* | Accumulator | BlockIndex 45 // block-tuple := CompressedHeader | CompressedBody | CompressedReceipts | TotalDifficulty 46 // 47 // Each basic element is its own entry: 48 // 49 // Version = { type: [0x65, 0x32], data: nil } 50 // CompressedHeader = { type: [0x03, 0x00], data: snappyFramed(rlp(header)) } 51 // CompressedBody = { type: [0x04, 0x00], data: snappyFramed(rlp(body)) } 52 // CompressedReceipts = { type: [0x05, 0x00], data: snappyFramed(rlp(receipts)) } 53 // TotalDifficulty = { type: [0x06, 0x00], data: uint256(header.total_difficulty) } 54 // AccumulatorRoot = { type: [0x07, 0x00], data: accumulator-root } 55 // BlockIndex = { type: [0x32, 0x66], data: block-index } 56 // 57 // Accumulator is computed by constructing an SSZ list of header-records of length at most 58 // 8192 and then calculating the hash_tree_root of that list. 59 // 60 // header-record := { block-hash: Bytes32, total-difficulty: Uint256 } 61 // accumulator := hash_tree_root([]header-record, 8192) 62 // 63 // BlockIndex stores relative offsets to each compressed block entry. The 64 // format is: 65 // 66 // block-index := starting-number | index | index | index ... | count 67 // 68 // starting-number is the first block number in the archive. Every index is a 69 // defined relative to beginning of the record. The total number of block 70 // entries in the file is recorded with count. 71 // 72 // Due to the accumulator size limit of 8192, the maximum number of blocks in 73 // an Era1 batch is also 8192. 74 type Builder struct { 75 w *e2store.Writer 76 startNum *uint64 77 startTd *big.Int 78 indexes []uint64 79 hashes []common.Hash 80 tds []*big.Int 81 written int 82 83 buf *bytes.Buffer 84 snappy *snappy.Writer 85 } 86 87 // NewBuilder returns a new Builder instance. 88 func NewBuilder(w io.Writer) *Builder { 89 buf := bytes.NewBuffer(nil) 90 return &Builder{ 91 w: e2store.NewWriter(w), 92 buf: buf, 93 snappy: snappy.NewBufferedWriter(buf), 94 } 95 } 96 97 // Add writes a compressed block entry and compressed receipts entry to the 98 // underlying e2store file. 99 func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int) error { 100 eh, err := rlp.EncodeToBytes(block.Header()) 101 if err != nil { 102 return err 103 } 104 eb, err := rlp.EncodeToBytes(block.Body()) 105 if err != nil { 106 return err 107 } 108 er, err := rlp.EncodeToBytes(receipts) 109 if err != nil { 110 return err 111 } 112 return b.AddRLP(eh, eb, er, block.NumberU64(), block.Hash(), td, block.Difficulty()) 113 } 114 115 // AddRLP writes a compressed block entry and compressed receipts entry to the 116 // underlying e2store file. 117 func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, hash common.Hash, td, difficulty *big.Int) error { 118 // Write Era1 version entry before first block. 119 if b.startNum == nil { 120 n, err := b.w.Write(TypeVersion, nil) 121 if err != nil { 122 return err 123 } 124 startNum := number 125 b.startNum = &startNum 126 b.startTd = new(big.Int).Sub(td, difficulty) 127 b.written += n 128 } 129 if len(b.indexes) >= MaxEra1Size { 130 return fmt.Errorf("exceeds maximum batch size of %d", MaxEra1Size) 131 } 132 133 b.indexes = append(b.indexes, uint64(b.written)) 134 b.hashes = append(b.hashes, hash) 135 b.tds = append(b.tds, td) 136 137 // Write block data. 138 if err := b.snappyWrite(TypeCompressedHeader, header); err != nil { 139 return err 140 } 141 if err := b.snappyWrite(TypeCompressedBody, body); err != nil { 142 return err 143 } 144 if err := b.snappyWrite(TypeCompressedReceipts, receipts); err != nil { 145 return err 146 } 147 148 // Also write total difficulty, but don't snappy encode. 149 btd := bigToBytes32(td) 150 n, err := b.w.Write(TypeTotalDifficulty, btd[:]) 151 b.written += n 152 if err != nil { 153 return err 154 } 155 156 return nil 157 } 158 159 // Finalize computes the accumulator and block index values, then writes the 160 // corresponding e2store entries. 161 func (b *Builder) Finalize() (common.Hash, error) { 162 if b.startNum == nil { 163 return common.Hash{}, errors.New("finalize called on empty builder") 164 } 165 // Compute accumulator root and write entry. 166 root, err := ComputeAccumulator(b.hashes, b.tds) 167 if err != nil { 168 return common.Hash{}, fmt.Errorf("error calculating accumulator root: %w", err) 169 } 170 n, err := b.w.Write(TypeAccumulator, root[:]) 171 b.written += n 172 if err != nil { 173 return common.Hash{}, fmt.Errorf("error writing accumulator: %w", err) 174 } 175 // Get beginning of index entry to calculate block relative offset. 176 base := int64(b.written) 177 178 // Construct block index. Detailed format described in Builder 179 // documentation, but it is essentially encoded as: 180 // "start | index | index | ... | count" 181 var ( 182 count = len(b.indexes) 183 index = make([]byte, 16+count*8) 184 ) 185 binary.LittleEndian.PutUint64(index, *b.startNum) 186 // Each offset is relative from the position it is encoded in the 187 // index. This means that even if the same block was to be included in 188 // the index twice (this would be invalid anyways), the relative offset 189 // would be different. The idea with this is that after reading a 190 // relative offset, the corresponding block can be quickly read by 191 // performing a seek relative to the current position. 192 for i, offset := range b.indexes { 193 relative := int64(offset) - base 194 binary.LittleEndian.PutUint64(index[8+i*8:], uint64(relative)) 195 } 196 binary.LittleEndian.PutUint64(index[8+count*8:], uint64(count)) 197 198 // Finally, write the block index entry. 199 if _, err := b.w.Write(TypeBlockIndex, index); err != nil { 200 return common.Hash{}, fmt.Errorf("unable to write block index: %w", err) 201 } 202 203 return root, nil 204 } 205 206 // snappyWrite is a small helper to take care snappy encoding and writing an e2store entry. 207 func (b *Builder) snappyWrite(typ uint16, in []byte) error { 208 var ( 209 buf = b.buf 210 s = b.snappy 211 ) 212 buf.Reset() 213 s.Reset(buf) 214 if _, err := b.snappy.Write(in); err != nil { 215 return fmt.Errorf("error snappy encoding: %w", err) 216 } 217 if err := s.Flush(); err != nil { 218 return fmt.Errorf("error flushing snappy encoding: %w", err) 219 } 220 n, err := b.w.Write(typ, b.buf.Bytes()) 221 b.written += n 222 if err != nil { 223 return fmt.Errorf("error writing e2store entry: %w", err) 224 } 225 return nil 226 }