diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index a947f35f2f..cd3690beb9 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -21,9 +21,12 @@ import ( "errors" "fmt" "os" + "path/filepath" + "regexp" "runtime" "slices" "strconv" + "strings" "sync/atomic" "time" @@ -39,6 +42,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/internal/era/eradl" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -190,7 +194,7 @@ This command dumps out the state for a given block (or latest, if none provided) `, } - pruneCommand = &cli.Command{ + pruneHistoryCommand = &cli.Command{ Action: pruneHistory, Name: "prune-history", Usage: "Prune blockchain history (block bodies and receipts) up to the merge block", @@ -201,6 +205,38 @@ The prune-history command removes historical block bodies and receipts from the blockchain database up to the merge block, while preserving block headers. This helps reduce storage requirements for nodes that don't need full historical data.`, } + + downloadEraCommand = &cli.Command{ + Action: downloadEra, + Name: "download-era", + Usage: "Fetches era1 files (pre-merge history) from an HTTP endpoint", + ArgsUsage: "", + Flags: slices.Concat(utils.DatabaseFlags, []cli.Flag{ + eraBlockFlag, + eraEpochFlag, + eraAllFlag, + eraServerFlag, + }), + } +) + +var ( + eraBlockFlag = &cli.StringFlag{ + Name: "block", + Usage: "Block number to fetch. (can also be a range -)", + } + eraEpochFlag = &cli.StringFlag{ + Name: "epoch", + Usage: "Epoch number to fetch (can also be a range -)", + } + eraAllFlag = &cli.BoolFlag{ + Name: "all", + Usage: "Download all available era1 files", + } + eraServerFlag = &cli.StringFlag{ + Name: "server", + Usage: "era1 server URL", + } ) // initGenesis will initialise the given JSON format genesis file and writes it as @@ -665,3 +701,83 @@ func pruneHistory(ctx *cli.Context) error { return nil } + +// downladEra is the era1 file downloader tool. +func downloadEra(ctx *cli.Context) error { + flags.CheckExclusive(ctx, eraBlockFlag, eraEpochFlag, eraAllFlag) + + // Resolve the network. + var network = "mainnet" + if utils.IsNetworkPreset(ctx) { + switch { + case ctx.IsSet(utils.MainnetFlag.Name): + case ctx.IsSet(utils.SepoliaFlag.Name): + network = "sepolia" + default: + return fmt.Errorf("unsupported network, no known era1 checksums") + } + } + + // Resolve the destination directory. + stack, _ := makeConfigNode(ctx) + defer stack.Close() + ancients := stack.ResolveAncient("chaindata", "") + dir := filepath.Join(ancients, "era") + + baseURL := ctx.String(eraServerFlag.Name) + if baseURL == "" { + return fmt.Errorf("need --%s flag to download", eraServerFlag.Name) + } + + l, err := eradl.New(baseURL, network) + if err != nil { + return err + } + switch { + case ctx.IsSet(eraAllFlag.Name): + return l.DownloadAll(dir) + + case ctx.IsSet(eraBlockFlag.Name): + s := ctx.String(eraBlockFlag.Name) + start, end, ok := parseRange(s) + if !ok { + return fmt.Errorf("invalid block range: %q", s) + } + return l.DownloadBlockRange(start, end, dir) + + case ctx.IsSet(eraEpochFlag.Name): + s := ctx.String(eraEpochFlag.Name) + start, end, ok := parseRange(s) + if !ok { + return fmt.Errorf("invalid epoch range: %q", s) + } + return l.DownloadEpochRange(start, end, dir) + + default: + return fmt.Errorf("specify one of --%s, --%s, or --%s to download", eraAllFlag.Name, eraBlockFlag.Name, eraEpochFlag.Name) + } +} + +func parseRange(s string) (start uint64, end uint64, ok bool) { + if m, _ := regexp.MatchString("[0-9]+", s); m { + start, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return 0, 0, false + } + end = start + return start, end, true + } + if m, _ := regexp.MatchString("[0-9]+-[0-9]+", s); m { + s1, s2, _ := strings.Cut(s, "-") + start, err := strconv.ParseUint(s1, 10, 64) + if err != nil { + return 0, 0, false + } + end, err = strconv.ParseUint(s2, 10, 64) + if err != nil { + return 0, 0, false + } + return start, end, true + } + return 0, 0, false +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 289030ae65..bdff96e369 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -225,7 +225,8 @@ func init() { removedbCommand, dumpCommand, dumpGenesisCommand, - pruneCommand, + pruneHistoryCommand, + downloadEraCommand, // See accountcmd.go: accountCommand, walletCommand,