mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
reject noreceipts, fixes & test
This commit is contained in:
parent
77d19df307
commit
8f4f38002f
2 changed files with 109 additions and 5 deletions
|
|
@ -22,6 +22,7 @@ import (
|
|||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
|
|
@ -165,6 +166,15 @@ func TestEre(t *testing.T) {
|
|||
if e.Count() != uint64(totalBlocks) {
|
||||
t.Fatalf("wrong block count: want %d, got %d", totalBlocks, e.Count())
|
||||
}
|
||||
// Verify component count: 4 when TD is stored (pre-merge or
|
||||
// transition), 3 otherwise (pure post-merge).
|
||||
wantComponents := uint64(3)
|
||||
if tt.preMerge > 0 {
|
||||
wantComponents = 4
|
||||
}
|
||||
if e.m.components != wantComponents {
|
||||
t.Fatalf("wrong component count: want %d, got %d", wantComponents, e.m.components)
|
||||
}
|
||||
|
||||
// Verify accumulator in file.
|
||||
if tt.accumulator {
|
||||
|
|
@ -339,6 +349,43 @@ func TestInitialTD(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestOpenRejectsNoreceiptsProfile verifies that Open() refuses to decode an
|
||||
// Ere file whose filename declares the unsupported "noreceipts" profile. The
|
||||
// positional reader can't safely interpret such a file because TD would be
|
||||
// shifted into the receipts slot.
|
||||
func TestOpenRejectsNoreceiptsProfile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
// Build a valid Ere file with default-profile contents, then rename it
|
||||
// to claim a noreceipts profile in its filename.
|
||||
src, err := os.CreateTemp(dir, "ere-src-*.ere")
|
||||
if err != nil {
|
||||
t.Fatalf("create temp file: %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
builder := NewBuilder(src)
|
||||
header := mustEncode(&types.Header{Number: big.NewInt(0), Difficulty: big.NewInt(1)})
|
||||
body := mustEncode(&types.Body{})
|
||||
receipts := mustEncode([]types.SlimReceipt{})
|
||||
if err := builder.AddRLP(header, body, receipts, 0, common.Hash{0}, big.NewInt(1), big.NewInt(1)); err != nil {
|
||||
t.Fatalf("AddRLP: %v", err)
|
||||
}
|
||||
if _, err := builder.Finalize(); err != nil {
|
||||
t.Fatalf("Finalize: %v", err)
|
||||
}
|
||||
|
||||
renamed := filepath.Join(dir, "mainnet-00000-deadbeef-noreceipts.ere")
|
||||
if err := os.Rename(src.Name(), renamed); err != nil {
|
||||
t.Fatalf("rename: %v", err)
|
||||
}
|
||||
if _, err := Open(renamed); err == nil {
|
||||
t.Fatal("expected Open to reject noreceipts profile")
|
||||
}
|
||||
}
|
||||
|
||||
func mustEncode(obj any) []byte {
|
||||
b, err := rlp.EncodeToBytes(obj)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ import (
|
|||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -48,8 +50,14 @@ func Filename(network string, epoch int, lastBlockHash common.Hash) string {
|
|||
return fmt.Sprintf("%s-%05d-%s-noproofs.ere", network, epoch, lastBlockHash.Hex()[2:10])
|
||||
}
|
||||
|
||||
// Open accesses the era file.
|
||||
// Open accesses the era file. The path is used to parse the profile postfix
|
||||
// (per the Ere spec filename convention); files written with the "noreceipts"
|
||||
// profile are rejected because the positional index reader assumes receipts
|
||||
// are present.
|
||||
func Open(path string) (*Era, error) {
|
||||
if err := checkProfile(filepath.Base(path)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -59,6 +67,10 @@ func Open(path string) (*Era, error) {
|
|||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := e.checkComponents(); err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
|
|
@ -72,16 +84,56 @@ func (e *Era) Close() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// From returns an Era backed by f.
|
||||
// From returns an Era backed by f. Since no filename is available, the profile
|
||||
// cannot be inspected; the component count is still validated against the
|
||||
// supported layouts (header, body, receipts, [td]).
|
||||
func From(f era.ReadAtSeekCloser) (era.Era, error) {
|
||||
e := &Era{f: f, s: e2store.NewReader(f)}
|
||||
if err := e.loadIndex(); err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := e.checkComponents(); err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// checkProfile inspects the profile postfix(es) in an Ere filename and rejects
|
||||
// any combination this reader can't safely decode. The reader maps components
|
||||
// by fixed positions (header, body, receipts, td?, proof?), so a file written
|
||||
// with the "noreceipts" profile would silently shift TD into the receipts slot.
|
||||
//
|
||||
// The Ere format itself does not require a particular filename, so this check
|
||||
// is permissive about non-conforming names: validation only kicks in when a
|
||||
// profile postfix is actually present.
|
||||
func checkProfile(name string) error {
|
||||
name = strings.TrimSuffix(name, ".ere")
|
||||
parts := strings.Split(name, "-")
|
||||
if len(parts) <= 3 {
|
||||
return nil // no profile postfix to validate
|
||||
}
|
||||
for _, p := range parts[3:] {
|
||||
if p == "noreceipts" {
|
||||
return fmt.Errorf("Ere file %q uses the noreceipts profile, which is not supported", name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkComponents verifies the file's component count matches what this reader
|
||||
// supports. The reader assumes the fixed positional layout
|
||||
// (header, body, receipts, td?, proof?), and the builder in this package only
|
||||
// produces files with 3 (post-merge) or 4 (pre-merge / transition) components.
|
||||
// Files with 2 (noreceipts) or 5 (proofs present) components are rejected.
|
||||
func (e *Era) checkComponents() error {
|
||||
if e.m.components < 3 || e.m.components > 4 {
|
||||
return fmt.Errorf("unsupported Ere component count %d (reader expects header, body, receipts, and optional total difficulty)", e.m.components)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start retrieves the starting block number.
|
||||
func (e *Era) Start() uint64 {
|
||||
return e.m.start
|
||||
|
|
@ -213,8 +265,8 @@ func (e *Era) InitialTD() (*big.Int, error) {
|
|||
return new(big.Int).Sub(firstTD, header.Difficulty), nil
|
||||
}
|
||||
|
||||
// Accumulator reads the accumulator entry in the Ere file if it exists.
|
||||
// Only pre-merge and merge-transition Ere files contain an accumulator entry.
|
||||
// Accumulator reads the accumulator entry if present. Only pre-merge and
|
||||
// merge-transition Ere files contain one.
|
||||
func (e *Era) Accumulator() (common.Hash, error) {
|
||||
entry, err := e.s.Find(era.TypeAccumulator)
|
||||
if err != nil {
|
||||
|
|
@ -289,7 +341,12 @@ type metadata struct {
|
|||
// componentType represents the integer form of a specific type that can be present in the era file.
|
||||
type componentType int
|
||||
|
||||
// header, body, receipts, td, and proof are the different types of components that can be present in the era file.
|
||||
// header, body, receipts, td, and proof are the different types of components
|
||||
// that can be present in the era file. The Ere spec defines receipts, td, and
|
||||
// proof as independently optional, but this reader maps components to their
|
||||
// position in the index using this fixed enum. That positional mapping is only
|
||||
// safe as long as receipts are present (no "noreceipts" profile) — Open() and
|
||||
// From() enforce this via checkProfile and checkComponents.
|
||||
const (
|
||||
header componentType = iota
|
||||
body
|
||||
|
|
|
|||
Loading…
Reference in a new issue