diff --git a/cmd/era/main.go b/cmd/era/main.go index bd5844eff2..6abdd84e83 100644 --- a/cmd/era/main.go +++ b/cmd/era/main.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/internal/era/execdb" "github.com/ethereum/go-ethereum/internal/era/onedb" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/flags" @@ -132,7 +133,7 @@ func block(ctx *cli.Context) error { return nil } -// info prints some high-level information about the era1 file. +// info prints some high-level information about the era file. func info(ctx *cli.Context) error { epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64) if err != nil { @@ -143,33 +144,34 @@ func info(ctx *cli.Context) error { return err } defer e.Close() - acc, err := e.Accumulator() - if err != nil { - return fmt.Errorf("error reading accumulator: %w", err) + var ( + accHex string + tdStr string + ) + if acc, err := e.Accumulator(); err == nil { + accHex = acc.Hex() } - td, err := e.InitialTD() - if err != nil { - return fmt.Errorf("error reading total difficulty: %w", err) + if td, err := e.InitialTD(); err == nil { + tdStr = td.String() } info := struct { - Accumulator common.Hash `json:"accumulator"` - TotalDifficulty *big.Int `json:"totalDifficulty"` - StartBlock uint64 `json:"startBlock"` - Count uint64 `json:"count"` + Accumulator string `json:"accumulator,omitempty"` + TotalDifficulty string `json:"totalDifficulty,omitempty"` + StartBlock uint64 `json:"startBlock"` + Count uint64 `json:"count"` }{ - acc, td, e.Start(), e.Count(), + accHex, tdStr, e.Start(), e.Count(), } b, _ := json.MarshalIndent(info, "", " ") fmt.Println(string(b)) return nil } -// open opens an era1 file at a certain epoch. -func open(ctx *cli.Context, epoch uint64) (*onedb.Era, error) { - var ( - dir = ctx.String(dirFlag.Name) - network = ctx.String(networkFlag.Name) - ) +// open opens an era file at a certain epoch. +func open(ctx *cli.Context, epoch uint64) (era.Era, error) { + dir := ctx.String(dirFlag.Name) + network := ctx.String(networkFlag.Name) + entries, err := era.ReadDir(dir, network) if err != nil { return nil, fmt.Errorf("error reading era dir: %w", err) @@ -177,8 +179,28 @@ func open(ctx *cli.Context, epoch uint64) (*onedb.Era, error) { if epoch >= uint64(len(entries)) { return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch) } - era, err := onedb.Open(filepath.Join(dir, entries[epoch])) - return era.(*onedb.Era), err + path := filepath.Join(dir, entries[epoch]) + return openByPath(path) +} + +// openByPath tries to open a single file as either eraE or era1 based on extension, +// falling back to the other reader if needed. +func openByPath(path string) (era.Era, error) { + switch strings.ToLower(filepath.Ext(path)) { + case ".erae": + if e, err := execdb.Open(path); err != nil { + return nil, err + } else { + return e, nil + } + case ".era1": + if e, err := onedb.Open(path); err != nil { + return nil, err + } else { + return e, nil + } + } + return nil, fmt.Errorf("unsupported or unreadable era file: %s", path) } // verify checks each era1 file in a directory to ensure it is well-formed and @@ -205,29 +227,68 @@ func verify(ctx *cli.Context) error { return fmt.Errorf("error reading %s: %w", dir, err) } - if len(entries) != len(roots) { - return errors.New("number of era1 files should match the number of accumulator hashes") + // Build the verification list respecting the rule: + // era1: must have accumulator, always verify + // erae: verify only if accumulator exists (pre-merge) + + // Build list of files to verify. + verify := make([]string, 0, len(entries)) + + for _, name := range entries { + path := filepath.Join(dir, name) + ext := strings.ToLower(filepath.Ext(name)) + + switch ext { + case ".era1": + e, err := onedb.Open(path) + if err != nil { + return fmt.Errorf("error opening era1 file %s: %w", name, err) + } + _, accErr := e.Accumulator() + e.Close() + if accErr != nil { + return fmt.Errorf("era1 file %s missing accumulator: %w", name, accErr) + } + verify = append(verify, path) + + case ".erae": + e, err := execdb.Open(path) + if err != nil { + return fmt.Errorf("error opening erae file %s: %w", name, err) + } + _, accErr := e.Accumulator() + e.Close() + if accErr == nil { + verify = append(verify, path) // pre-merge only + } + default: + return fmt.Errorf("unsupported era file: %s", name) + } + } + + if len(verify) != len(roots) { + return fmt.Errorf("mismatch between eras to verify (%d) and provided roots (%d)", len(verify), len(roots)) } // Verify each epoch matches the expected root. for i, want := range roots { // Wrap in function so defers don't stack. err := func() error { - name := entries[i] - e, err := onedb.Open(filepath.Join(dir, name)) - eraPointer := e.(*onedb.Era) + path := verify[i] + name := filepath.Base(path) + e, err := openByPath(path) if err != nil { - return fmt.Errorf("error opening era1 file %s: %w", name, err) + return fmt.Errorf("error opening era file %s: %w", name, err) } defer e.Close() // Read accumulator and check against expected. - if got, err := eraPointer.Accumulator(); err != nil { + if got, err := e.Accumulator(); err != nil { return fmt.Errorf("error retrieving accumulator for %s: %w", name, err) } else if got != want { return fmt.Errorf("invalid root %s: got %s, want %s", name, got, want) } // Recompute accumulator. - if err := checkAccumulator(eraPointer); err != nil { + if err := checkAccumulator(e); err != nil { return fmt.Errorf("error verify era1 file %s: %w", name, err) } // Give the user some feedback that something is happening. @@ -246,7 +307,7 @@ func verify(ctx *cli.Context) error { } // checkAccumulator verifies the accumulator matches the data in the Era. -func checkAccumulator(e *onedb.Era) error { +func checkAccumulator(e era.Era) error { var ( err error want common.Hash diff --git a/internal/era/era.go b/internal/era/era.go index 056231ab2b..4e391640b9 100644 --- a/internal/era/era.go +++ b/internal/era/era.go @@ -63,6 +63,8 @@ type Era interface { GetBlockByNumber(num uint64) (*types.Block, error) GetRawBodyByNumber(num uint64) ([]byte, error) GetRawReceiptsByNumber(num uint64) ([]byte, error) + InitialTD() (*big.Int, error) + Accumulator() (common.Hash, error) } // ReadDir reads all the era1 files in a directory for a given network. diff --git a/internal/era/execdb/reader.go b/internal/era/execdb/reader.go index 44cd30b821..886554422e 100644 --- a/internal/era/execdb/reader.go +++ b/internal/era/execdb/reader.go @@ -227,6 +227,16 @@ func (e *Era) InitialTD() (*big.Int, error) { return td.Sub(td, header.Difficulty), nil } +// Accumulator reads the accumulator entry in the EraE file if it exists. +// Note that one premerge erae files will contain an accumulator entry. +func (e *Era) Accumulator() (common.Hash, error) { + entry, err := e.s.Find(era.TypeAccumulator) + if err != nil { + return common.Hash{}, err + } + return common.BytesToHash(entry.Value), nil +} + // loadIndex loads in the index table containing all offsets and caches it. func (e *Era) loadIndex() error { var err error