diff --git a/cmd/era/main.go b/cmd/era/main.go index 43279e7001..3abe54a8b4 100644 --- a/cmd/era/main.go +++ b/cmd/era/main.go @@ -183,11 +183,11 @@ func open(ctx *cli.Context, epoch uint64) (era.Era, error) { return openByPath(path) } -// openByPath tries to open a single file as either eraE or era1 based on extension, +// openByPath tries to open a single file as either Ere 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": + case ".ere": if e, err := execdb.Open(path); err != nil { return nil, err } else { @@ -229,7 +229,7 @@ func verify(ctx *cli.Context) error { // Build the verification list respecting the rule: // era1: must have accumulator, always verify - // erae: verify only if accumulator exists (pre-merge) + // ere: verify only if accumulator exists (pre-merge / transition) // Build list of files to verify. verify := make([]string, 0, len(entries)) @@ -251,15 +251,15 @@ func verify(ctx *cli.Context) error { } verify = append(verify, path) - case ".erae": + case ".ere": e, err := execdb.Open(path) if err != nil { - return fmt.Errorf("error opening erae file %s: %w", name, err) + return fmt.Errorf("error opening ere file %s: %w", name, err) } _, accErr := e.Accumulator() e.Close() if accErr == nil { - verify = append(verify, path) // pre-merge only + verify = append(verify, path) // pre-merge / transition only } default: return fmt.Errorf("unsupported era file: %s", name) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 0aacb0878a..884c380799 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -533,10 +533,10 @@ func importHistory(ctx *cli.Context) error { switch format { case "era1", "era": from = onedb.From - case "erae": + case "ere": from = execdb.From default: - return fmt.Errorf("unknown --era.format %q (expected 'era1' or 'erae')", format) + return fmt.Errorf("unknown --era.format %q (expected 'era1' or 'ere')", format) } if err := utils.ImportHistory(chain, dir, network, from); err != nil { return err @@ -582,11 +582,11 @@ func exportHistory(ctx *cli.Context) error { case "era1", "era": newBuilder = func(w io.Writer) era.Builder { return onedb.NewBuilder(w) } filename = func(network string, epoch int, root common.Hash) string { return onedb.Filename(network, epoch, root) } - case "erae": + case "ere": newBuilder = func(w io.Writer) era.Builder { return execdb.NewBuilder(w) } filename = func(network string, epoch int, root common.Hash) string { return execdb.Filename(network, epoch, root) } default: - return fmt.Errorf("unknown archive format %q (use 'era1' or 'erae')", format) + return fmt.Errorf("unknown archive format %q (use 'era1' or 'ere')", format) } if err := utils.ExportHistory(chain, dir, uint64(first), uint64(last), newBuilder, filename); err != nil { utils.Fatalf("Export error: %v\n", err) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cc4c3bff5c..c8f841e1c2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1110,7 +1110,7 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. // Era flags are a group of flags related to the era archive format. EraFormatFlag = &cli.StringFlag{ Name: "era.format", - Usage: "Archive format: 'era1' or 'erae'", + Usage: "Archive format: 'era1' or 'ere'", } ) diff --git a/cmd/utils/history_test.go b/cmd/utils/history_test.go index 6631946129..56375f9ff5 100644 --- a/cmd/utils/history_test.go +++ b/cmd/utils/history_test.go @@ -53,7 +53,7 @@ func TestHistoryImportAndExport(t *testing.T) { from func(f era.ReadAtSeekCloser) (era.Era, error) }{ {"era1", onedb.NewBuilder, onedb.Filename, onedb.From}, - {"erae", execdb.NewBuilder, execdb.Filename, execdb.From}, + {"ere", execdb.NewBuilder, execdb.Filename, execdb.From}, } { t.Run(tt.name, func(t *testing.T) { var ( diff --git a/internal/era/era.go b/internal/era/era.go index a3c8465bc4..0aae75e4bb 100644 --- a/internal/era/era.go +++ b/internal/era/era.go @@ -29,7 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// Type constants for the e2store entries in the Era1 and EraE formats. +// Type constants for the e2store entries in the Era1 and Ere formats. var ( TypeVersion uint16 = 0x3265 TypeCompressedHeader uint16 = 0x03 @@ -40,10 +40,9 @@ var ( TypeCompressedSlimReceipts uint16 = 0x0a // uses eth/69 encoding TypeProof uint16 = 0x0b TypeBlockIndex uint16 = 0x3266 - TypeComponentIndex uint16 = 0x3267 + TypeDynamicBlockIndex uint16 = 0x3267 MaxSize = 8192 - // headerSize uint64 = 8 ) type ReadAtSeekCloser interface { @@ -93,7 +92,7 @@ type Builder interface { // Finalize writes all collected entries and returns the epoch identifier. // For Era1 (onedb): returns the accumulator root. - // For EraE (execdb): returns the last block hash. + // For Ere (execdb): returns the last block hash. Finalize() (common.Hash, error) // Accumulator returns the accumulator root after Finalize has been called. @@ -115,7 +114,7 @@ type Era interface { } // ReadDir reads all the era files in a directory for a given network. -// Format: --.erae or --.era1 +// Format: --(-)*.ere or --.era1 func ReadDir(dir, network string) ([]string, error) { entries, err := os.ReadDir(dir) @@ -129,14 +128,16 @@ func ReadDir(dir, network string) ([]string, error) { ) for _, entry := range entries { ext := path.Ext(entry.Name()) - if ext != ".erae" && ext != ".era1" { + if ext != ".ere" && ext != ".era1" { continue } if dirType == "" { dirType = ext } parts := strings.Split(entry.Name(), "-") - if len(parts) != 3 || parts[0] != network { + // Ere files may carry an optional profile postfix (e.g. "-noproofs"), + // so the filename has at least 3 dash-separated parts. + if len(parts) < 3 || parts[0] != network { // Invalid era filename, skip. continue } diff --git a/internal/era/execdb/builder.go b/internal/era/execdb/builder.go index 6246b9caae..4c656ab2e0 100644 --- a/internal/era/execdb/builder.go +++ b/internal/era/execdb/builder.go @@ -16,40 +16,44 @@ package execdb -// EraE file format specification. +// Ere file format specification. +// +// See https://github.com/eth-clients/e2store-format-specs/blob/main/formats/ere.md. // // The format can be summarized with the following expression: // -// eraE := Version | CompressedHeader* | CompressedBody* | CompressedSlimReceipts* | TotalDifficulty* | other-entries* | Accumulator? | ComponentIndex +// ere := Version | CompressedHeader+ | CompressedBody+ | CompressedSlimReceipts* | Proof* | TotalDifficulty* | other-entries* | Accumulator? | DynamicBlockIndex // // Each basic element is its own e2store entry: // -// Version = { type: 0x3265, data: nil } -// CompressedHeader = { type: 0x03, data: snappyFramed(rlp(header)) } -// CompressedBody = { type: 0x04, data: snappyFramed(rlp(body)) } -// CompressedSlimReceipts = { type: 0x0a, data: snappyFramed(rlp([tx-type, post-state-or-status, cumulative-gas, logs])) } -// TotalDifficulty = { type: 0x06, data: uint256 (header.total_difficulty) } -// AccumulatorRoot = { type: 0x07, data: hash_tree_root(List(HeaderRecord, 8192)) } -// ComponentIndex = { type: 0x3267, data: component-index } +// Version = { type: [0x65, 0x32], data: nil } +// CompressedHeader = { type: [0x03, 0x00], data: snappyFramed(rlp(header)) } +// CompressedBody = { type: [0x04, 0x00], data: snappyFramed(rlp(body)) } +// CompressedSlimReceipts = { type: [0x0a, 0x00], data: snappyFramed(rlp([tx-type, post-state-or-status, cumulative-gas, logs])) } +// TotalDifficulty = { type: [0x06, 0x00], data: uint256(header.total_difficulty) } +// Proof = { type: [0x0b, 0x00], data: snappyFramed(rlp([proof-type, ssz(proof)])) } +// AccumulatorRoot = { type: [0x07, 0x00], data: hash_tree_root(List(HeaderRecord, 8192)) } +// DynamicBlockIndex = { type: [0x67, 0x32], data: block-index } // // Notes: // - TotalDifficulty is present for pre-merge and merge transition epochs. // For pure post-merge epochs, TotalDifficulty is omitted entirely. // - In merge transition epochs, post-merge blocks store the final total // difficulty (the TD at which the merge occurred). -// - AccumulatorRoot is only written for pre-merge epochs. +// - AccumulatorRoot is only written for pre-merge or transition epochs. // - HeaderRecord is defined in the Portal Network specification. -// - Proofs (type 0x09) are defined in the spec but not yet supported in this implementation. +// - Proof entries are recommended by the spec but not produced by this +// implementation; files written here use the "noproofs" profile postfix. // -// ComponentIndex stores relative offsets to each block's components: +// DynamicBlockIndex stores relative offsets to each block's components: // -// component-index := starting-number | indexes | indexes | ... | component-count | count -// indexes := header-offset | body-offset | receipts-offset | td-offset? +// block-index := starting-number | indexes | indexes | ... | component-count | count +// indexes := header-index | body-index | receipts-index? | difficulty-index? | proof-index? // // All values are little-endian uint64. // // Due to the accumulator size limit of 8192, the maximum number of blocks in an -// EraE file is also 8192. +// Ere file is also 8192. import ( "bytes" @@ -67,7 +71,7 @@ import ( "github.com/golang/snappy" ) -// Builder is used to build an EraE e2store file. It collects block entries and +// Builder is used to build an Ere e2store file. It collects block entries and // writes them to the underlying e2store.Writer. type Builder struct { w *e2store.Writer @@ -326,7 +330,7 @@ func (b *Builder) writeIndex(o *offsets) error { write(uint64(componentCount)) write(uint64(count)) - n, err := b.w.Write(era.TypeComponentIndex, buf.Bytes()) + n, err := b.w.Write(era.TypeDynamicBlockIndex, buf.Bytes()) b.written += uint64(n) return err } diff --git a/internal/era/execdb/era_test.go b/internal/era/execdb/era_test.go index f66931b9ed..22c5b47f90 100644 --- a/internal/era/execdb/era_test.go +++ b/internal/era/execdb/era_test.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -func TestEraE(t *testing.T) { +func TestEre(t *testing.T) { t.Parallel() tests := []struct { @@ -74,7 +74,7 @@ func TestEraE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - f, err := os.CreateTemp(t.TempDir(), "erae-test") + f, err := os.CreateTemp(t.TempDir(), "ere-test") if err != nil { t.Fatalf("error creating temp file: %v", err) } @@ -295,7 +295,7 @@ func TestEraE(t *testing.T) { func TestInitialTD(t *testing.T) { t.Parallel() - f, err := os.CreateTemp(t.TempDir(), "erae-initial-td-test") + f, err := os.CreateTemp(t.TempDir(), "ere-initial-td-test") if err != nil { t.Fatalf("error creating temp file: %v", err) } diff --git a/internal/era/execdb/reader.go b/internal/era/execdb/reader.go index d0aaad1748..9968cddc69 100644 --- a/internal/era/execdb/reader.go +++ b/internal/era/execdb/reader.go @@ -39,10 +39,13 @@ type Era struct { m metadata // metadata for the Era file } -// Filename returns a recognizable filename for an EraE file. +// Filename returns a recognizable filename for an Ere file. // The filename uses the last block hash to uniquely identify the epoch's content. +// +// Files produced by this builder do not include Proof entries, so the +// "noproofs" profile postfix is appended per the Ere spec. func Filename(network string, epoch int, lastBlockHash common.Hash) string { - return fmt.Sprintf("%s-%05d-%s.erae", network, epoch, lastBlockHash.Hex()[2:10]) + return fmt.Sprintf("%s-%05d-%s-noproofs.ere", network, epoch, lastBlockHash.Hex()[2:10]) } // Open accesses the era file. @@ -210,8 +213,8 @@ func (e *Era) InitialTD() (*big.Int, error) { return new(big.Int).Sub(firstTD, 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. +// Accumulator reads the accumulator entry in the Ere file if it exists. +// Only pre-merge and merge-transition Ere files contain an accumulator entry. func (e *Era) Accumulator() (common.Hash, error) { entry, err := e.s.Find(era.TypeAccumulator) if err != nil {