Implemented the proof interface, refactored code, and implemented comments

This commit is contained in:
shantichanal 2025-07-14 16:59:26 +02:00 committed by lightclient
parent 02b439cdea
commit ff0837ab29
No known key found for this signature in database
GPG key ID: 657913021EF45A6A
3 changed files with 352 additions and 222 deletions

View file

@ -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
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
for _, data := range b.buff.headers {
off, err := b.addEntry(TypeCompressedHeader, data, true)
if err != nil {
return common.Hash{}, fmt.Errorf("headers: %w", 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
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]))
pos += 8
}
if len(b.proofs) > 0 {
binary.LittleEndian.PutUint64(idx[pos:], rel(b.proofoffsets[i]))
pos += 8
}
}
basePosition := 8 + i*componentCount*8
binary.LittleEndian.PutUint64(idx[pos:], compcount)
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
binary.LittleEndian.PutUint64(idx[pos:], count)
if n, err := b.w.Write(TypeBlockIndex, idx); err != nil {
}
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(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)

View file

@ -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
@ -57,7 +122,6 @@ 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
}
@ -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
n := first + i
off, err := e.headerOff(n)
if err != nil {
return nil, err
}
if er = rlp.Decode(snappy.NewReader(r), &hdrs[i]); er != nil {
err = er
return
r, _, err := e.s.ReaderAt(TypeCompressedHeader, int64(off))
if err != nil {
return nil, err
}
var h types.Header
if err := rlp.Decode(snappy.NewReader(r), &h); err != nil {
return nil, err
}
if wantBody {
r, _, er := e.s.ReaderAt(TypeCompressedBody, int64(e.bodyoff[id]))
if er != nil {
err = er
return
out[i] = &h
}
if er = rlp.Decode(snappy.NewReader(r), &bods[i]); er != nil {
err = er
return
}
}
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)
}
}
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
}

View file

@ -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)