Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(daemon): add import command to download pruned snapshots #1424

Merged
merged 11 commits into from
Jul 19, 2024
40 changes: 40 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"syscall"
"time"

"github.com/k0kubun/go-ansi"
"github.com/manifoldco/promptui"
"github.com/pactus-project/pactus/config"
"github.com/pactus-project/pactus/crypto"
Expand All @@ -29,6 +30,7 @@ import (
"github.com/pactus-project/pactus/wallet"
"github.com/pactus-project/pactus/wallet/addresspath"
"github.com/pactus-project/pactus/wallet/vault"
"github.com/schollz/progressbar/v3"
)

const (
Expand Down Expand Up @@ -121,6 +123,20 @@ func PromptInput(label string) string {
return result
}

// PromptSelect prompts create choice menu for select by user.
func PromptSelect(label string, items []string) int {
prompt := promptui.Select{
Label: label,
Items: items,
Pointer: promptui.PipeCursor,
}

choice, _, err := prompt.Run()
FatalErrorCheck(err)

return choice
}

// PromptInputWithSuggestion prompts the user for an input string with a suggestion.
func PromptInputWithSuggestion(label, suggestion string) string {
prompt := promptui.Prompt{
Expand Down Expand Up @@ -617,3 +633,27 @@ func MakeValidatorKey(walletInstance *wallet.Wallet, valAddrsInfo []vault.Addres

return valKeys, nil
}

func TerminalProgressBar(totalSize, barWidth int, showBytes bool) *progressbar.ProgressBar {
if barWidth < 15 {
barWidth = 15
}

opts := []progressbar.Option{
progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowBytes(showBytes),
progressbar.OptionSetWidth(barWidth),
progressbar.OptionSetElapsedTime(false),
progressbar.OptionSetPredictTime(false),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerHead: "[green]>[reset]",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}),
}

return progressbar.NewOptions(totalSize, opts...)
}
149 changes: 149 additions & 0 deletions cmd/daemon/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package main

import (
"fmt"
"os"
"path/filepath"
"time"

"github.com/gofrs/flock"
"github.com/pactus-project/pactus/cmd"
"github.com/pactus-project/pactus/genesis"
"github.com/pactus-project/pactus/util"
"github.com/spf13/cobra"
)

func buildImportCmd(parentCmd *cobra.Command) {
importCmd := &cobra.Command{
Use: "import",
Short: "download and import pruned data",
}
parentCmd.AddCommand(importCmd)
Ja7ad marked this conversation as resolved.
Show resolved Hide resolved

workingDirOpt := addWorkingDirOption(importCmd)
serverAddrOpt := importCmd.Flags().String("server-addr", "https://download.pactus.org",
"import server address")

importCmd.Run = func(c *cobra.Command, _ []string) {
workingDir, err := filepath.Abs(*workingDirOpt)
cmd.FatalErrorCheck(err)

err = os.Chdir(workingDir)
cmd.FatalErrorCheck(err)

conf, gen, err := cmd.MakeConfig(workingDir)
cmd.FatalErrorCheck(err)

lockFilePath := filepath.Join(workingDir, ".pactus.lock")
fileLock := flock.New(lockFilePath)

locked, err := fileLock.TryLock()
cmd.FatalErrorCheck(err)

if !locked {
cmd.PrintWarnMsgf("Could not lock '%s', another instance is running?", lockFilePath)

return
}

storeDir, _ := filepath.Abs(conf.Store.StorePath())
if !util.IsDirNotExistsOrEmpty(storeDir) {
cmd.PrintErrorMsgf("The data directory is not empty: %s", conf.Store.StorePath())

return
}

snapshotURL := *serverAddrOpt

switch gen.ChainType() {
case genesis.Mainnet:
snapshotURL += "/mainnet/"
case genesis.Testnet:
snapshotURL += "/testnet/"
case genesis.Localnet:
cmd.PrintErrorMsgf("Unsupported chain type: %s", gen.ChainType())

return
}

metadata, err := cmd.GetSnapshotMetadata(c.Context(), snapshotURL)
if err != nil {
cmd.PrintErrorMsgf("Failed to get snapshot metadata: %s", err)

return
}

snapshots := make([]string, 0, len(metadata))

for _, m := range metadata {
item := fmt.Sprintf("snapshot %s (%s)",
parseTime(m.CreatedAt).Format("2006-01-02"),
util.FormatBytesToHumanReadable(m.TotalSize),
)

snapshots = append(snapshots, item)
}

cmd.PrintLine()

choice := cmd.PromptSelect("Please select a snapshot", snapshots)

selected := metadata[choice]
tmpDir := util.TempDirPath()
extractPath := fmt.Sprintf("%s/data", tmpDir)

err = os.MkdirAll(extractPath, 0o750)
cmd.FatalErrorCheck(err)

cmd.PrintLine()

zipFileList := cmd.DownloadManager(
c.Context(),
&selected,
snapshotURL,
tmpDir,
downloadProgressBar,
)

for _, zFile := range zipFileList {
Ja7ad marked this conversation as resolved.
Show resolved Hide resolved
err := cmd.ExtractAndStoreFile(zFile, extractPath)
cmd.FatalErrorCheck(err)
}

err = os.MkdirAll(filepath.Dir(conf.Store.StorePath()), 0o750)
cmd.FatalErrorCheck(err)

err = cmd.CopyAllFiles(extractPath, conf.Store.StorePath())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better we move the whole extracted directory

cmd.FatalErrorCheck(err)

err = os.RemoveAll(tmpDir)
cmd.FatalErrorCheck(err)

_ = fileLock.Unlock()

cmd.PrintLine()
cmd.PrintLine()
cmd.PrintInfoMsgf("✅ Your node successfully imported prune data.")
cmd.PrintLine()
cmd.PrintInfoMsgf("You can start the node by running this command:")
cmd.PrintInfoMsgf("./pactus-daemon start -w %v", workingDir)
}
}

func downloadProgressBar(fileName string, totalSize, downloaded int64, _ float64) {
bar := cmd.TerminalProgressBar(int(totalSize), 30, true)
bar.Describe(fileName)
err := bar.Add(int(downloaded))
cmd.FatalErrorCheck(err)
}

func parseTime(dateString string) time.Time {
const layout = "2006-01-02T15:04:05.000000"

parsedTime, err := time.Parse(layout, dateString)
if err != nil {
return time.Time{}
}

return parsedTime
}
1 change: 1 addition & 0 deletions cmd/daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func main() {
buildInitCmd(rootCmd)
buildStartCmd(rootCmd)
buildPruneCmd(rootCmd)
buildImportCmd(rootCmd)

err := rootCmd.Execute()
if err != nil {
Expand Down
16 changes: 4 additions & 12 deletions cmd/daemon/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/gofrs/flock"
"github.com/pactus-project/pactus/cmd"
Expand Down Expand Up @@ -115,15 +114,8 @@ func buildPruneCmd(parentCmd *cobra.Command) {
}

func pruningProgressBar(prunedCount, skippedCount, totalCount uint32) {
percentage := float64(prunedCount+skippedCount) / float64(totalCount) * 100
if percentage > 100 {
percentage = 100
}

barLength := 40
filledLength := int(float64(barLength) * percentage / 100)

bar := strings.Repeat("=", filledLength) + strings.Repeat(" ", barLength-filledLength)
fmt.Printf("\r [%s] %.0f%% Pruned: %d | Skipped: %d", //nolint
bar, percentage, prunedCount, skippedCount)
bar := cmd.TerminalProgressBar(int(totalCount), 30, false)
bar.Describe(fmt.Sprintf("Pruned: %d | Skipped: %d", prunedCount, skippedCount))
err := bar.Add(int(prunedCount + skippedCount))
cmd.FatalErrorCheck(err)
}
Loading
Loading