From ff0837ab294c92f0e8c61b7c157e8d1a601cc986 Mon Sep 17 00:00:00 2001 From: shantichanal <158101918+shantichanal@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:59:26 +0200 Subject: [PATCH] Implemented the proof interface, refactored code, and implemented comments --- internal/era2/builder.go | 192 +++++++++++---------- internal/era2/era.go | 339 +++++++++++++++++++++++--------------- internal/era2/era_test.go | 43 +++++ 3 files changed, 352 insertions(+), 222 deletions(-) diff --git a/internal/era2/builder.go b/internal/era2/builder.go index 38ecd371b1..42ee727d37 100644 --- a/internal/era2/builder.go +++ b/internal/era2/builder.go @@ -57,18 +57,20 @@ const ( type proofvar uint16 -const ( - ProofNone proofvar = iota - ProofHHA - ProofRoots - ProofCapella - ProofDeneb -) +type buffer struct { + headers [][]byte + bodies [][]byte + receipts [][]byte + proofs [][]byte + tds [][]byte +} -// Proof bundles variant + compressed bytes. -type Proof struct { - Variant proofvar - Data []byte +type offsets struct { + headers []uint64 + bodys []uint64 + receipts []uint64 + proofoffsets []uint64 + tdoff []uint64 } type Builder struct { @@ -76,22 +78,12 @@ type Builder struct { buf *bytes.Buffer sn *snappy.Writer - headers [][]byte - bodies [][]byte - receipts [][]byte - proofs [][]byte - tds [][]byte - tdsint []*big.Int - hashes []common.Hash - - headeroffsets []uint64 - bodyoffsets []uint64 - receiptoffsets []uint64 - proofoffsets []uint64 - tdoff []uint64 - - prooftype proofvar + buff buffer + off offsets + prooftype proofvar + tdsint []*big.Int + hashes []common.Hash startNum *uint64 writtenBytes uint64 } @@ -107,20 +99,23 @@ func NewBuilder(w io.Writer) *Builder { } // Add writes a block entry, its reciepts, and optionally its proofs as well into the e2store file. -func (b *Builder) Add(header types.Header, body types.Body, receipts types.Receipts, td *big.Int, proof *Proof) error { - var pv proofvar = ProofNone - var pData []byte +func (b *Builder) Add(header types.Header, body types.Body, receipts types.Receipts, td *big.Int, proof Proof) error { + pv := proofVariantOf(proof) // variant code (or proofNone) + var ep []byte // encoded proof payload + if proof != nil { - pv, pData = proof.Variant, proof.Data - if pv == ProofNone || len(pData) == 0 { - return fmt.Errorf("invalid proof: variant=%d len=%d", pv, len(pData)) + var buf bytes.Buffer + if err := proof.EncodeRLP(&buf); err != nil { + return fmt.Errorf("encode proof: %w", err) } + ep = buf.Bytes() } - if len(b.headers) == 0 { + if len(b.buff.headers) == 0 { b.prooftype = pv } else if pv != b.prooftype { - return fmt.Errorf("cannot mix proof variants: first=%d now=%d", b.prooftype, pv) + return fmt.Errorf("cannot mix proof variants: first=%d now=%d", + b.prooftype, pv) } eh, err := rlp.EncodeToBytes(&header) @@ -136,31 +131,27 @@ func (b *Builder) Add(header types.Header, body types.Body, receipts types.Recei return fmt.Errorf("encode receipts: %w", err) } - var ep []byte - if pv != ProofNone { - ep, err = rlp.EncodeToBytes([]interface{}{uint16(pv), proof.Data}) - if err != nil { - return fmt.Errorf("encode proof: %w", err) - } - } - - return b.AddRLP(eh, eb, er, ep, header.Number.Uint64(), header.Hash(), td) + return b.AddRLP( + eh, eb, er, ep, + header.Number.Uint64(), + header.Hash(), td, + ) } // AddRLP takes the RLP encoded block components and writes them to the underlying e2store file func (b *Builder) AddRLP(headerRLP []byte, bodyRLP []byte, receipts []byte, proof []byte, blockNum uint64, blockHash common.Hash, td *big.Int) error { - if len(b.headers) >= MaxEraESize { + if len(b.buff.headers) >= MaxEraESize { return fmt.Errorf("exceeds MaxEraESize %d", MaxEraESize) } - b.headers = append(b.headers, headerRLP) - b.bodies = append(b.bodies, bodyRLP) - b.receipts = append(b.receipts, receipts) - b.tds = append(b.tds, uint256LE(td)) + b.buff.headers = append(b.buff.headers, headerRLP) + b.buff.bodies = append(b.buff.bodies, bodyRLP) + b.buff.receipts = append(b.buff.receipts, receipts) + b.buff.tds = append(b.buff.tds, uint256LE(td)) b.tdsint = append(b.tdsint, new(big.Int).Set(td)) b.hashes = append(b.hashes, blockHash) if proof != nil { - b.proofs = append(b.proofs, proof) + b.buff.proofs = append(b.buff.proofs, proof) } //Write Era2 version before writing any blocks. @@ -181,40 +172,64 @@ func (b *Builder) Finalize() (common.Hash, error) { if b.startNum == nil { return common.Hash{}, errors.New("no blocks added, cannot finalize") } - var err error - if err := b.flushKind(TypeCompressedHeader, b.headers, true, &b.headeroffsets); err != nil { - return common.Hash{}, err - } - if err := b.flushKind(TypeCompressedBody, b.bodies, true, &b.bodyoffsets); err != nil { - return common.Hash{}, err - } - if err := b.flushKind(TypeCompressedReceipts, b.receipts, true, &b.receiptoffsets); err != nil { - return common.Hash{}, err + for _, data := range b.buff.headers { + off, err := b.addEntry(TypeCompressedHeader, data, true) + if err != nil { + return common.Hash{}, fmt.Errorf("headers: %w", err) + } + b.off.headers = append(b.off.headers, off) } - if len(b.tds) > 0 { - if err := b.flushKind(TypeTotalDifficulty, b.tds, false, &b.tdoff); err != nil { - return common.Hash{}, err + for _, data := range b.buff.bodies { + off, err := b.addEntry(TypeCompressedBody, data, true) + if err != nil { + return common.Hash{}, fmt.Errorf("bodies: %w", err) + } + b.off.bodys = append(b.off.bodys, off) + } + + for _, data := range b.buff.receipts { + off, err := b.addEntry(TypeCompressedReceipts, data, true) + if err != nil { + return common.Hash{}, fmt.Errorf("receipts: %w", err) + } + b.off.receipts = append(b.off.receipts, off) + } + + if len(b.buff.tds) > 0 { + for _, data := range b.buff.tds { + off, err := b.addEntry(TypeTotalDifficulty, data, false) + if err != nil { + return common.Hash{}, fmt.Errorf("total-difficulty: %w", err) + } + b.off.tdoff = append(b.off.tdoff, off) } } + if b.prooftype != ProofNone { - if err := b.flushKind(TypeProof, b.proofs, true, &b.proofoffsets); err != nil { - return common.Hash{}, err + for _, data := range b.buff.proofs { + off, err := b.addEntry(TypeProof, data, true) + if err != nil { + return common.Hash{}, fmt.Errorf("proofs: %w", err) + } + b.off.proofoffsets = append(b.off.proofoffsets, off) } } var accRoot common.Hash if len(b.hashes) > 0 { + var err error accRoot, err = ComputeAccumulator(b.hashes, b.tdsint) if err != nil { - return common.Hash{}, fmt.Errorf("error calculating accumulator root: %w", err) + return common.Hash{}, fmt.Errorf("compute accumulator: %w", err) } if n, err := b.w.Write(TypeAccumulatorRoot, accRoot[:]); err != nil { - return common.Hash{}, fmt.Errorf("error writing accumulator root: %w", err) + return common.Hash{}, fmt.Errorf("write accumulator: %w", err) } else { b.writtenBytes += uint64(n) } } + return accRoot, b.writeIndex() } @@ -282,41 +297,40 @@ func (b *Builder) flushKind(typ uint16, list [][]byte, useSnappy bool, dst *[]ui // write index takes all the offset table and writes it to the file // the index table contains all offsets to specific block entries func (b *Builder) writeIndex() error { - count := uint64(len(b.headeroffsets)) - compcount := uint64(3) - if len(b.tds) > 0 { - compcount++ + count := uint64(len(b.off.headers)) + componentCount := uint64(3) + if len(b.buff.tds) > 0 { + componentCount++ } - if len(b.proofs) > 0 { - compcount++ + if len(b.buff.proofs) > 0 { + componentCount++ } - idx := make([]byte, 8+count*8*compcount+16) //8 for start block, 8 per property per block, 16 for the number of properties and the number of blocks - binary.LittleEndian.PutUint64(idx, *b.startNum) + index := make([]byte, 8+count*8*componentCount+16) //8 for start block, 8 per property per block, 16 for the number of properties and the number of blocks + binary.LittleEndian.PutUint64(index, *b.startNum) base := int64(b.writtenBytes) - pos := 8 rel := func(abs uint64) uint64 { return uint64(int64(abs) - base) } for i := uint64(0); i < count; i++ { - binary.LittleEndian.PutUint64(idx[pos:], rel(b.headeroffsets[i])) - pos += 8 - binary.LittleEndian.PutUint64(idx[pos:], rel(b.bodyoffsets[i])) - pos += 8 - binary.LittleEndian.PutUint64(idx[pos:], rel(b.receiptoffsets[i])) - pos += 8 - if len(b.tds) > 0 { - binary.LittleEndian.PutUint64(idx[pos:], rel(b.tdoff[i])) + basePosition := 8 + i*componentCount*8 + + binary.LittleEndian.PutUint64(index[basePosition:], rel(b.off.headers[i])) + binary.LittleEndian.PutUint64(index[basePosition+8:], rel(b.off.bodys[i])) + binary.LittleEndian.PutUint64(index[basePosition+16:], rel(b.off.receipts[i])) + + pos := uint64(24) + if len(b.buff.tds) > 0 { + binary.LittleEndian.PutUint64(index[basePosition+pos:], rel(b.off.tdoff[i])) pos += 8 } - if len(b.proofs) > 0 { - binary.LittleEndian.PutUint64(idx[pos:], rel(b.proofoffsets[i])) - pos += 8 + if len(b.buff.proofs) > 0 { + binary.LittleEndian.PutUint64(index[basePosition+pos:], rel(b.off.proofoffsets[i])) } } + indexEnd := 8 + count*componentCount*8 - binary.LittleEndian.PutUint64(idx[pos:], compcount) - pos += 8 - binary.LittleEndian.PutUint64(idx[pos:], count) - if n, err := b.w.Write(TypeBlockIndex, idx); err != nil { + binary.LittleEndian.PutUint64(index[indexEnd+0:], componentCount) + binary.LittleEndian.PutUint64(index[indexEnd+8:], count) + if n, err := b.w.Write(TypeBlockIndex, index); err != nil { return err } else { b.writtenBytes += uint64(n) diff --git a/internal/era2/era.go b/internal/era2/era.go index aa0440f93a..233d4417a4 100644 --- a/internal/era2/era.go +++ b/internal/era2/era.go @@ -21,6 +21,22 @@ type metadata struct { length int64 // length of the file in bytes } +const ( + ProofNone proofvar = iota + proofHHA + proofRoots + proofCapella + proofDeneb +) + +const ( + compHeader = 0 + compBody = 1 + compReceipts = 2 + compTD = 3 + compProof = 4 +) + type BlockProofHistoricalHashesAccumulator [15]common.Hash // 15 * 32 = 480 bytes // BlockProofHistoricalRoots – Altair / Bellatrix historical_roots branch. @@ -47,6 +63,55 @@ type BlockProofHistoricalSummariesDeneb struct { Slot uint64 // 8 => 840 bytes } +type Proof interface { + EncodeRLP(w io.Writer) error + DecodeRlP(s *rlp.Stream) error + Variant() proofvar +} + +type hhaAlias BlockProofHistoricalHashesAccumulator // alias ⇒ no EncodeRLP method + +func (p *BlockProofHistoricalHashesAccumulator) EncodeRLP(w io.Writer) error { + payload := []interface{}{uint16(proofHHA), hhaAlias(*p)} + return rlp.Encode(w, payload) +} + +func (p *BlockProofHistoricalHashesAccumulator) Variant() proofvar { return proofHHA } + +type rootsAlias BlockProofHistoricalRoots + +func (p *BlockProofHistoricalRoots) EncodeRLP(w io.Writer) error { + payload := []interface{}{uint16(proofRoots), rootsAlias(*p)} + return rlp.Encode(w, payload) +} + +func (*BlockProofHistoricalRoots) Variant() proofvar { return proofRoots } + +type capellaAlias BlockProofHistoricalSummariesCapella + +func (p *BlockProofHistoricalSummariesCapella) EncodeRLP(w io.Writer) error { + payload := []interface{}{uint16(proofCapella), capellaAlias(*p)} + return rlp.Encode(w, payload) +} + +func (*BlockProofHistoricalSummariesCapella) Variant() proofvar { return proofCapella } + +type denebAlias BlockProofHistoricalSummariesDeneb + +func (p *BlockProofHistoricalSummariesDeneb) EncodeRLP(w io.Writer) error { + payload := []interface{}{uint16(proofDeneb), denebAlias(*p)} + return rlp.Encode(w, payload) +} + +func (*BlockProofHistoricalSummariesDeneb) Variant() proofvar { return proofDeneb } + +func proofVariantOf(p Proof) proofvar { + if p == nil { + return ProofNone + } + return p.Variant() +} + type ReadAtSeekCloser interface { io.ReaderAt io.Seeker @@ -54,12 +119,11 @@ type ReadAtSeekCloser interface { } type Era struct { - f ReadAtSeekCloser - s *e2store.Reader - m metadata // metadata for the Era file - headeroff, bodyoff, receiptsoff, tdoff, proofsoff []uint64 // offsets for each entry type - indstart int64 - rootheader uint64 // offset of the root header in the file if present + f ReadAtSeekCloser + s *e2store.Reader + m metadata // metadata for the Era file + indstart int64 + rootheader uint64 // offset of the root header in the file if present } // Opens era file @@ -98,11 +162,11 @@ func (e *Era) Count() uint64 { // retrieves the block if present within the era file func (e *Era) GetBlockByNumber(blockNum uint64) (*types.Block, error) { - h, err := e.getHeader(blockNum) + h, err := e.GetHeader(blockNum) if err != nil { return nil, err } - b, err := e.getBody(blockNum) + b, err := e.GetBody(blockNum) if err != nil { return nil, err } @@ -110,46 +174,46 @@ func (e *Era) GetBlockByNumber(blockNum uint64) (*types.Block, error) { } // retrieves header from era file through the cached offset table -func (e *Era) getHeader(blockNum uint64) (*types.Header, error) { - if blockNum < e.m.start || blockNum >= e.m.start+e.m.count { - return nil, fmt.Errorf("block number %d out of range [%d, %d)", blockNum, e.m.start, e.m.start+e.m.count) - } - r, _, err := e.s.ReaderAt(TypeCompressedHeader, int64(e.headeroff[blockNum-e.m.start])) +func (e *Era) GetHeader(num uint64) (*types.Header, error) { + off, err := e.headerOff(num) if err != nil { return nil, err } - r = snappy.NewReader(r) + r, _, err := e.s.ReaderAt(TypeCompressedHeader, int64(off)) + if err != nil { + return nil, err + } + + r = snappy.NewReader(r) var h types.Header return &h, rlp.Decode(r, &h) } // retrieves body from era file through cached offset table -func (e *Era) getBody(blockNum uint64) (*types.Body, error) { - if blockNum < e.m.start || blockNum >= e.m.start+e.m.count { - return nil, fmt.Errorf("block number %d out of range [%d, %d)", blockNum, e.m.start, e.m.start+e.m.count) - } - r, _, err := e.s.ReaderAt(TypeCompressedBody, int64(e.bodyoff[blockNum-e.m.start])) +func (e *Era) GetBody(num uint64) (*types.Body, error) { + off, err := e.bodyOff(num) if err != nil { return nil, err } - r = snappy.NewReader(r) + r, _, err := e.s.ReaderAt(TypeCompressedBody, int64(off)) + if err != nil { + return nil, err + } + + r = snappy.NewReader(r) var b types.Body return &b, rlp.Decode(r, &b) } // retrieves td from era file through cached offset table func (e *Era) getTD(blockNum uint64) (*big.Int, error) { - if blockNum < e.m.start || blockNum >= e.m.start+e.m.count { - return nil, fmt.Errorf("block number %d out of range [%d, %d)", - blockNum, e.m.start, e.m.start+e.m.count) + off, err := e.tdOff(blockNum) + if err != nil { + return nil, err } - if len(e.tdoff) == 0 { - return nil, fmt.Errorf("total-difficulty section not present") - } - - r, _, err := e.s.ReaderAt(TypeTotalDifficulty, int64(e.tdoff[blockNum-e.m.start])) + r, _, err := e.s.ReaderAt(TypeTotalDifficulty, int64(off)) if err != nil { return nil, err } @@ -160,10 +224,11 @@ func (e *Era) getTD(blockNum uint64) (*big.Int, error) { // retrieves the raw body frame in bytes of a specific block func (e *Era) GetRawBodyFrameByNumber(blockNum uint64) ([]byte, error) { - if blockNum < e.m.start || blockNum >= e.m.start+e.m.count { - return nil, fmt.Errorf("block number %d out of range [%d, %d)", blockNum, e.m.start, e.m.start+e.m.count) + off, err := e.bodyOff(blockNum) + if err != nil { + return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedBody, int64(e.bodyoff[blockNum-e.m.start])) + r, _, err := e.s.ReaderAt(TypeCompressedBody, int64(off)) if err != nil { return nil, err } @@ -172,10 +237,11 @@ func (e *Era) GetRawBodyFrameByNumber(blockNum uint64) ([]byte, error) { // retrieves the raw receipts frame in bytes of a specific block func (e *Era) GetRawReceiptsFrameByNumber(blockNum uint64) ([]byte, error) { - if blockNum < e.m.start || blockNum >= e.m.start+e.m.count { - return nil, fmt.Errorf("block number %d out of range [%d, %d)", blockNum, e.m.start, e.m.start+e.m.count) + off, err := e.rcptOff(blockNum) + if err != nil { + return nil, err } - r, _, err := e.s.ReaderAt(TypeCompressedReceipts, int64(e.receiptsoff[blockNum-e.m.start])) + r, _, err := e.s.ReaderAt(TypeCompressedReceipts, int64(off)) if err != nil { return nil, err } @@ -184,13 +250,11 @@ func (e *Era) GetRawReceiptsFrameByNumber(blockNum uint64) ([]byte, error) { // retrieves the raw proof frame in bytes of a specific block proof func (e *Era) GetRawProofFrameByNumber(blockNum uint64) ([]byte, error) { - if len(e.proofsoff) == 0 { - return nil, fmt.Errorf("proofs section not present") + off, err := e.proofOff(blockNum) + if err != nil { + return nil, err } - if blockNum < e.m.start || blockNum >= e.m.start+e.m.count { - return nil, fmt.Errorf("block number %d out of range [%d, %d)", blockNum, e.m.start, e.m.start+e.m.count) - } - r, _, err := e.s.ReaderAt(TypeProof, int64(e.proofsoff[blockNum-e.m.start])) + r, _, err := e.s.ReaderAt(TypeProof, int64(off)) if err != nil { return nil, err } @@ -232,33 +296,6 @@ func (e *Era) loadIndex() error { return err } - toAbs := func(i int) uint64 { - rel := binary.LittleEndian.Uint64(offBytes[i*8 : i*8+8]) - return uint64(int64(rel) + e.indstart) - } - - e.headeroff = make([]uint64, e.m.count) - e.bodyoff = make([]uint64, e.m.count) - e.receiptsoff = make([]uint64, e.m.count) - if num > 3 { - e.tdoff = make([]uint64, e.m.count) - } - if num > 4 { - e.proofsoff = make([]uint64, e.m.count) - } - for i := uint64(0); i < e.m.count; i++ { - base := int(i * uint64(num)) - e.headeroff[i] = toAbs(base) - e.bodyoff[i] = toAbs(base + 1) - e.receiptsoff[i] = toAbs(base + 2) - if num > 3 { - e.tdoff[i] = toAbs(base + 3) - } - if num > 4 { - e.proofsoff[i] = toAbs(base + 4) - } - } - var off int64 = 0 // start at byte-0 of file for off < e.indstart { // never enter the Block-Index TLV @@ -275,80 +312,116 @@ func (e *Era) loadIndex() error { return nil } -// retrieves a range of block from any start block to n number of blocks -// the components retrieved can be customized depending on the properties wanted -func (e *Era) BatchRange(first, count uint64, wantHdr, wantBody, wantRec, wantPrf bool) (hdrs []*types.Header, bods []*types.Body, recs []types.Receipts, prfs [][]byte, err error) { +// Getter methods to calculate offset of a specific component in the file. +func (e *Era) headerOff(num uint64) (uint64, error) { return e.indexOffset(num, compHeader) } +func (e *Era) bodyOff(num uint64) (uint64, error) { return e.indexOffset(num, compBody) } +func (e *Era) rcptOff(num uint64) (uint64, error) { return e.indexOffset(num, compReceipts) } +func (e *Era) tdOff(num uint64) (uint64, error) { return e.indexOffset(num, compTD) } +func (e *Era) proofOff(num uint64) (uint64, error) { return e.indexOffset(num, compProof) } + +// calculates offset to a certain component for a block number within a file. +func (e *Era) indexOffset(n uint64, comp int) (uint64, error) { + if n < e.m.start || n >= e.m.start+e.m.count { + return 0, fmt.Errorf("block %d out of range [%d,%d)", n, e.m.start, e.m.start+e.m.count) + } + if comp >= int(e.m.components) { + return 0, fmt.Errorf("component %d not present", comp) + } + + rec := (n-e.m.start)*e.m.components + uint64(comp) + pos := e.indstart + 8 + 8 + int64(rec*8) + + var buf [8]byte + if _, err := e.f.ReadAt(buf[:], pos); err != nil { + return 0, err + } + rel := binary.LittleEndian.Uint64(buf[:]) + return uint64(int64(rel) + e.indstart), nil +} + +// GetHeaders returns RLP-decoded headers. +func (e *Era) GetHeaders(first, count uint64) ([]*types.Header, error) { if count == 0 { - err = fmt.Errorf("count must be > 0") - return + return nil, fmt.Errorf("count must be > 0") } if first < e.m.start || first+count > e.m.start+e.m.count { - err = fmt.Errorf("range [%d,%d) out of bounds", first, first+count) - return - } - - idx := first - e.m.start - if wantHdr { - hdrs = make([]*types.Header, count) - } - if wantBody { - bods = make([]*types.Body, count) - } - if wantRec { - recs = make([]types.Receipts, count) - } - if wantPrf { - prfs = make([][]byte, count) + return nil, fmt.Errorf("range [%d,%d) out of bounds", first, first+count) } + out := make([]*types.Header, count) for i := uint64(0); i < count; i++ { - id := idx + i - - if wantHdr { - r, _, er := e.s.ReaderAt(TypeCompressedHeader, int64(e.headeroff[id])) - if er != nil { - err = er - return - } - if er = rlp.Decode(snappy.NewReader(r), &hdrs[i]); er != nil { - err = er - return - } + n := first + i + off, err := e.headerOff(n) + if err != nil { + return nil, err } - if wantBody { - r, _, er := e.s.ReaderAt(TypeCompressedBody, int64(e.bodyoff[id])) - if er != nil { - err = er - return - } - if er = rlp.Decode(snappy.NewReader(r), &bods[i]); er != nil { - err = er - return - } + r, _, err := e.s.ReaderAt(TypeCompressedHeader, int64(off)) + if err != nil { + return nil, err } - if wantRec { - r, _, er := e.s.ReaderAt(TypeCompressedReceipts, int64(e.receiptsoff[id])) - if er != nil { - err = er - return - } - if er = rlp.Decode(snappy.NewReader(r), &recs[i]); er != nil { - err = er - return - } - } - if wantPrf { - if len(e.proofsoff) == 0 { - err = fmt.Errorf("proofs section not present") - return - } - r, _, er := e.s.ReaderAt(TypeProof, int64(e.proofsoff[id])) - if er != nil { - err = er - return - } - prfs[i], _ = io.ReadAll(r) + var h types.Header + if err := rlp.Decode(snappy.NewReader(r), &h); err != nil { + return nil, err } + out[i] = &h } - return + return out, nil +} + +// GetBodies returns RLP-decoded bodies. +func (e *Era) GetBodies(first, count uint64) ([]*types.Body, error) { + if count == 0 { + return nil, fmt.Errorf("count must be > 0") + } + if first < e.m.start || first+count > e.m.start+e.m.count { + return nil, fmt.Errorf("range [%d,%d) out of bounds", first, first+count) + } + + out := make([]*types.Body, count) + for i := uint64(0); i < count; i++ { + n := first + i + off, err := e.bodyOff(n) + if err != nil { + return nil, err + } + r, _, err := e.s.ReaderAt(TypeCompressedBody, int64(off)) + if err != nil { + return nil, err + } + var b types.Body + if err := rlp.Decode(snappy.NewReader(r), &b); err != nil { + return nil, err + } + out[i] = &b + } + return out, nil +} + +// GetReceipts returns RLP-decoded receipts. +func (e *Era) GetReceipts(first, count uint64) ([]types.Receipts, error) { + if count == 0 { + return nil, fmt.Errorf("count must be > 0") + } + if first < e.m.start || first+count > e.m.start+e.m.count { + return nil, fmt.Errorf("range [%d,%d) out of bounds", first, first+count) + } + + out := make([]types.Receipts, count) + for i := uint64(0); i < count; i++ { + n := first + i + off, err := e.rcptOff(n) + if err != nil { + return nil, err + } + r, _, err := e.s.ReaderAt(TypeCompressedReceipts, int64(off)) + if err != nil { + return nil, err + } + var rc types.Receipts + if err := rlp.Decode(snappy.NewReader(r), &rc); err != nil { + return nil, err + } + out[i] = rc + } + return out, nil } diff --git a/internal/era2/era_test.go b/internal/era2/era_test.go index 218b477c0b..1dfdb3f6a5 100644 --- a/internal/era2/era_test.go +++ b/internal/era2/era_test.go @@ -83,6 +83,49 @@ func TestEra2Builder(t *testing.T) { } defer era.Close() + hdrs, err := era.GetHeaders(0, 128) + if err != nil { + t.Fatalf("BatchHeaders full: %v", err) + } + bods, err := era.GetBodies(0, 128) + if err != nil { + t.Fatalf("BatchBodies full: %v", err) + } + recs, err := era.GetReceipts(0, 128) + if err != nil { + t.Fatalf("BatchReceipts full: %v", err) + } + + for i := 0; i < 128; i++ { + if hdrs[i].Hash() != chain.headers[i].Hash() { + t.Fatalf("batch header %d mismatch", i) + } + if !bytes.Equal( + mustEncode(chain.bodies[i]), + mustEncode(bods[i]), + ) { + t.Fatalf("batch body %d mismatch", i) + } + if !bytes.Equal( + mustEncode(chain.receipts[i]), + mustEncode(recs[i]), + ) { + t.Fatalf("batch receipts %d mismatch", i) + } + } + + const start, cnt = 50, 10 + winHdrs, err := era.GetHeaders(start, cnt) + if err != nil { + t.Fatalf("BatchHeaders window: %v", err) + } + for j := 0; j < cnt; j++ { + idx := start + j + if winHdrs[j].Hash() != chain.headers[idx].Hash() { + t.Fatalf("window header %d mismatch", idx) + } + } + for i := 0; i < 128; i++ { bn := uint64(i) gotBlock, err := era.GetBlockByNumber(bn)