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

Add db revert cmd #2216

Merged
merged 14 commits into from
Oct 16, 2024
94 changes: 80 additions & 14 deletions cmd/juno/dbcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"github.com/spf13/cobra"
)

const (
dbRevertToBlockF = "to-block"
)

type DBInfo struct {
Network string `json:"network"`
ChainHeight uint64 `json:"chain_height"`
Expand All @@ -33,7 +37,7 @@
}

dbCmd.PersistentFlags().String(dbPathF, defaultDBPath, dbPathUsage)
dbCmd.AddCommand(DBInfoCmd(), DBSizeCmd())
dbCmd.AddCommand(DBInfoCmd(), DBSizeCmd(), DBRevertCmd())
return dbCmd
}

Expand All @@ -55,21 +59,29 @@
}
}

func DBRevertCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "revert",
Short: "Revert current head to given position",
Long: `This subcommand revert all data related to all blocks till given so it becomes new head.`,
RunE: dbRevert,
}
cmd.Flags().Uint64(dbRevertToBlockF, 0, "New head (this block won't be reverted)")

return cmd
}

func dbInfo(cmd *cobra.Command, args []string) error {
dbPath, err := cmd.Flags().GetString(dbPathF)
if err != nil {
return err
}

if _, err = os.Stat(dbPath); os.IsNotExist(err) {
fmt.Fprintln(cmd.OutOrStdout(), "Database path does not exist")
return nil
}

database, err := pebble.New(dbPath)
database, err := openDB(dbPath)
if err != nil {
return fmt.Errorf("open DB: %w", err)
return err

Check warning on line 82 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L82

Added line #L82 was not covered by tests
}
defer database.Close()

chain := blockchain.New(database, nil)
info := DBInfo{}
Expand Down Expand Up @@ -110,6 +122,50 @@
return nil
}

func dbRevert(cmd *cobra.Command, args []string) error {
dbPath, err := cmd.Flags().GetString(dbPathF)
if err != nil {
return err

Check warning on line 128 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L128

Added line #L128 was not covered by tests
}

revertToBlock, err := cmd.Flags().GetUint64(dbRevertToBlockF)
if err != nil {
return err

Check warning on line 133 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L133

Added line #L133 was not covered by tests
}

if revertToBlock == 0 {
return fmt.Errorf("--%v cannot be 0", dbRevertToBlockF)

Check warning on line 137 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L137

Added line #L137 was not covered by tests
}

database, err := openDB(dbPath)
if err != nil {
return err

Check warning on line 142 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L142

Added line #L142 was not covered by tests
}
defer database.Close()

for {
chain := blockchain.New(database, nil)
head, err := chain.Head()
if err != nil {
return fmt.Errorf("failed to get the latest block information: %v", err)

Check warning on line 150 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L150

Added line #L150 was not covered by tests
}

if head.Number == revertToBlock {
fmt.Fprintf(cmd.OutOrStdout(), "Successfully reverted all blocks to %d\n", revertToBlock)
break
}

err = chain.RevertHead()
if err != nil {
return fmt.Errorf("failed to revert head at block %d: %v", head.Number, err)

Check warning on line 160 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L160

Added line #L160 was not covered by tests
}

fmt.Fprintf(cmd.OutOrStdout(), "Reverted head at block %d\n", head.Number)
}

return nil
}

func dbSize(cmd *cobra.Command, args []string) error {
dbPath, err := cmd.Flags().GetString(dbPathF)
if err != nil {
Expand All @@ -120,15 +176,11 @@
return fmt.Errorf("--%v cannot be empty", dbPathF)
}

if _, err = os.Stat(dbPath); os.IsNotExist(err) {
fmt.Fprintln(cmd.OutOrStdout(), "Database path does not exist")
return nil
}

pebbleDB, err := pebble.New(dbPath)
pebbleDB, err := openDB(dbPath)
if err != nil {
return err
}
defer pebbleDB.Close()

var (
totalSize utils.DataSize
Expand Down Expand Up @@ -201,3 +253,17 @@

return "unknown"
}

func openDB(path string) (db.DB, error) {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return nil, fmt.Errorf("database path does not exist")

Check warning on line 260 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L260

Added line #L260 was not covered by tests
}

database, err := pebble.New(path)
if err != nil {
return nil, fmt.Errorf("failed to open db: %w", err)

Check warning on line 265 in cmd/juno/dbcmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/juno/dbcmd.go#L265

Added line #L265 was not covered by tests
}

return database, nil
}
66 changes: 55 additions & 11 deletions cmd/juno/dbcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main_test

import (
"context"
"strconv"
"testing"

"github.com/NethermindEth/juno/blockchain"
Expand All @@ -12,6 +13,7 @@ import (
adaptfeeder "github.com/NethermindEth/juno/starknetdata/feeder"
"github.com/NethermindEth/juno/utils"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -27,27 +29,69 @@ func TestDBCmd(t *testing.T) {
cmd := juno.DBSizeCmd()
executeCmdInDB(t, cmd)
})

t.Run("revert db by 1 block", func(t *testing.T) {
network := utils.Mainnet

const (
syncToBlock = uint64(2)
revertToBlock = syncToBlock - 1
)

cmd := juno.DBRevertCmd()
cmd.Flags().String("db-path", "", "")

dbPath := prepareDB(t, &network, syncToBlock)

require.NoError(t, cmd.Flags().Set("db-path", dbPath))
require.NoError(t, cmd.Flags().Set("to-block", strconv.Itoa(int(revertToBlock))))
require.NoError(t, cmd.Execute())

// unfortunately we cannot use blockchain from prepareDB because
// inside revert cmd another pebble instance is used which will panic if there are other instances
// that use the same db path
db, err := pebble.New(dbPath)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, db.Close())
})

chain := blockchain.New(db, &network)
block, err := chain.Head()
require.NoError(t, err)
assert.Equal(t, revertToBlock, block.Number)
})
}

func executeCmdInDB(t *testing.T, cmd *cobra.Command) {
cmd.Flags().String("db-path", "", "")

client := feeder.NewTestClient(t, &utils.Mainnet)
gw := adaptfeeder.New(client)
block0, err := gw.BlockByNumber(context.Background(), 0)
require.NoError(t, err)
dbPath := prepareDB(t, &utils.Mainnet, 0)

stateUpdate0, err := gw.StateUpdate(context.Background(), 0)
require.NoError(t, err)
require.NoError(t, cmd.Flags().Set("db-path", dbPath))
require.NoError(t, cmd.Execute())
}

func prepareDB(t *testing.T, network *utils.Network, syncToBlock uint64) string {
client := feeder.NewTestClient(t, network)
gw := adaptfeeder.New(client)

dbPath := t.TempDir()
testDB, err := pebble.New(dbPath)
require.NoError(t, err)

chain := blockchain.New(testDB, &utils.Mainnet)
require.NoError(t, chain.Store(block0, &emptyCommitments, stateUpdate0, nil))
testDB.Close()
chain := blockchain.New(testDB, network)

require.NoError(t, cmd.Flags().Set("db-path", dbPath))
require.NoError(t, cmd.Execute())
for blockNumber := uint64(0); blockNumber <= syncToBlock; blockNumber++ {
block, err := gw.BlockByNumber(context.Background(), blockNumber)
require.NoError(t, err)

stateUpdate, err := gw.StateUpdate(context.Background(), blockNumber)
require.NoError(t, err)

require.NoError(t, chain.Store(block, &emptyCommitments, stateUpdate, nil))
}
require.NoError(t, testDB.Close())

return dbPath
}
1 change: 1 addition & 0 deletions docs/docs/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Juno provides several subcommands to perform specific tasks or operations. Here
- `db`: Perform database-related operations
- `db info`: Retrieve information about the database.
- `db size`: Calculate database size information for each data type.
- `db revert`: Reverts the database to a specific block number.

To use a subcommand, append it when running Juno:

Expand Down