Skip to content

Commit 7fe1cbb

Browse files
Merge pull request #6452 from oasisprotocol/martin/feature/storage-inspect-cmd
go/oasis-node/storage: Add new inspect command
2 parents 081b777 + b47eb6c commit 7fe1cbb

5 files changed

Lines changed: 342 additions & 57 deletions

File tree

.changelog/6427.feature.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
go/oasis-node: Add new storage inspect command
2+
3+
The new command enables inspecting storage databases without starting
4+
all the node services. It is in particular useful for the node operators
5+
doing maintenance, sanity checking and or troubleshooting.

docs/oasis-node/cli.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,35 @@ way they guarantee the node is healthy when it starts.
387387

388388
Following successful pruning, to release disk space, they are encouraged to run
389389
[the compaction command](#compact-experimental).
390+
391+
### inspect
392+
393+
Run (when the node is not running):
394+
395+
```sh
396+
oasis-node storage inspect --config /path/to/config/file
397+
```
398+
399+
to inspect consensus and runtime databases:
400+
401+
```sh
402+
Consensus:
403+
State DB:
404+
Ok: true
405+
Latest height: 25499708
406+
Last retained height: 16817956
407+
Block history:
408+
Ok: true
409+
Latest height: 25499708
410+
Last retained height: 16817956
411+
Runtimes:
412+
000000000000000000000000000000000000000000000000f80306c9858e7279
413+
State DB:
414+
Ok: true
415+
Latest round: 9735938
416+
Last retained round: 1357486
417+
Light History:
418+
Ok: true
419+
Latest round: 9735938
420+
Last retained round: 1357486
421+
```

go/oasis-node/cmd/storage/dbs.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package storage
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
7+
cmtCfg "github.com/cometbft/cometbft/config"
8+
"github.com/cometbft/cometbft/state"
9+
"github.com/cometbft/cometbft/store"
10+
11+
"github.com/oasisprotocol/oasis-core/go/common"
12+
"github.com/oasisprotocol/oasis-core/go/config"
13+
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/abci"
14+
cmtCommon "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/common"
15+
cmtDB "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/db"
16+
runtimeConfig "github.com/oasisprotocol/oasis-core/go/runtime/config"
17+
"github.com/oasisprotocol/oasis-core/go/runtime/history"
18+
"github.com/oasisprotocol/oasis-core/go/storage/api"
19+
"github.com/oasisprotocol/oasis-core/go/worker/storage"
20+
)
21+
22+
func openConsensusNodeDB(dataDir string) (api.NodeDB, func(), error) {
23+
ldb, ndb, _, err := abci.InitStateStorage(
24+
&abci.ApplicationConfig{
25+
DataDir: filepath.Join(dataDir, cmtCommon.StateDir),
26+
StorageBackend: config.GlobalConfig.Storage.Backend,
27+
MemoryOnlyStorage: false,
28+
ReadOnlyStorage: false,
29+
DisableCheckpointer: true,
30+
},
31+
)
32+
if err != nil {
33+
return nil, nil, fmt.Errorf("failed to initialize ABCI storage backend: %w", err)
34+
}
35+
36+
// Close and Cleanup both only close NodeDB. Still closing both explicitly,
37+
// to prevent resource leaks if things change in the future.
38+
close := func() {
39+
ndb.Close()
40+
ldb.Cleanup()
41+
}
42+
43+
return ndb, close, nil
44+
}
45+
46+
func openConsensusBlockstore(dataDir string) (*store.BlockStore, error) {
47+
cmtConfig := cmtCfg.DefaultConfig()
48+
cmtConfig.SetRoot(filepath.Join(dataDir, cmtCommon.StateDir))
49+
50+
dbProvider, err := cmtDB.Provider()
51+
if err != nil {
52+
return nil, fmt.Errorf("failed to obtain db provider: %w", err)
53+
}
54+
55+
blockstoreDB, err := cmtDB.OpenBlockstoreDB(dbProvider, cmtConfig)
56+
if err != nil {
57+
return nil, fmt.Errorf("failed to open blockstore: %w", err)
58+
}
59+
blockstore := store.NewBlockStore(blockstoreDB)
60+
61+
return blockstore, nil
62+
}
63+
64+
func openConsensusStatestore(dataDir string) (state.Store, error) {
65+
cmtConfig := cmtCfg.DefaultConfig()
66+
cmtConfig.SetRoot(filepath.Join(dataDir, cmtCommon.StateDir))
67+
68+
dbProvider, err := cmtDB.Provider()
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to obtain db provider: %w", err)
71+
}
72+
stateDB, err := cmtDB.OpenStateDB(dbProvider, cmtConfig)
73+
if err != nil {
74+
return nil, fmt.Errorf("failed to open state db: %w", err)
75+
}
76+
return cmtDB.OpenStateStore(stateDB), nil
77+
}
78+
79+
func openRuntimeStateDB(dataDir string, runtimeID common.Namespace) (api.NodeDB, error) {
80+
rtDir := runtimeConfig.GetRuntimeStateDir(dataDir, runtimeID)
81+
backend, err := storage.NewLocalBackend(rtDir, runtimeID)
82+
if err != nil {
83+
return nil, fmt.Errorf("failed to open storage backend (runtimeID: %s): %w", runtimeID, err)
84+
}
85+
return backend.NodeDB(), err
86+
}
87+
88+
func openRuntimeLightHistory(dataDir string, rt common.Namespace) (history.History, error) {
89+
rtDir := runtimeConfig.GetRuntimeStateDir(dataDir, rt)
90+
history, err := history.New(rt, rtDir, history.NewNonePrunerFactory(), true)
91+
if err != nil {
92+
return nil, fmt.Errorf("failed to open new light history: %w", err)
93+
}
94+
return history, nil
95+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package storage
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/oasisprotocol/oasis-core/go/common"
10+
cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common"
11+
roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
12+
"github.com/oasisprotocol/oasis-core/go/runtime/registry"
13+
)
14+
15+
// Status contains results of inspecting all of the node's databases.
16+
type Status struct {
17+
// Consensus is the consensus part of the status.
18+
Consensus ConsensusStatus `json:"consensus"`
19+
// Runtimes is the runtimes part of the status.
20+
Runtimes map[common.Namespace]RuntimeStatus `json:"runtimes"`
21+
}
22+
23+
// ConsensusStatus summarizes the state of the consensus databases.
24+
type ConsensusStatus struct {
25+
// StateDB is the status of the consensus state db (Merkelized key-value store).
26+
StateDB DBStatus `json:"state_db"`
27+
// BlockHistory is the status of the consensus block store.
28+
BlockHistory DBStatus `json:"block_history"`
29+
}
30+
31+
// RuntimeStatus summarizes the state of the runtime databases.
32+
type RuntimeStatus struct {
33+
// StateDB is the status of the runtime state db (Merkelized key-value store).
34+
StateDB DBStatus `json:"state_db"`
35+
// LightHistory is the status of the runtime light history.
36+
LightHistory DBStatus `json:"light_history"`
37+
}
38+
39+
// DBStatus is a database status.
40+
type DBStatus struct {
41+
// Ok is true when the db exists and there was no error inspecting it.
42+
Ok bool `json:"ok"`
43+
// LatestVersion is the version of the most recent item stored in the corresponding db.
44+
LatestVersion uint64 `json:"latest_version"`
45+
// LastRetainedVersion is the version of the oldest item stored in the corresponding db.
46+
LastRetainedVersion uint64 `json:"last_retained_version"`
47+
}
48+
49+
// Inspect inspects node's databases and returns a corresponding storage Status.
50+
//
51+
// The node should not be running once this command is called.
52+
func Inspect(ctx context.Context, dataDir string, runtimes []common.Namespace) Status {
53+
var status Status
54+
status.Runtimes = make(map[common.Namespace]RuntimeStatus)
55+
56+
func() {
57+
ndb, close, err := openConsensusNodeDB(dataDir)
58+
if err != nil {
59+
logger.Error("failed to open consensus NodeDB", "err", err)
60+
return
61+
}
62+
defer close()
63+
status.Consensus.StateDB.LatestVersion, status.Consensus.StateDB.Ok = ndb.GetLatestVersion()
64+
status.Consensus.StateDB.LastRetainedVersion = ndb.GetEarliestVersion()
65+
}()
66+
67+
func() {
68+
blockstore, err := openConsensusBlockstore(dataDir)
69+
if err != nil {
70+
logger.Error("failed to open consensus blockstore", "err", err)
71+
return
72+
}
73+
defer blockstore.Close()
74+
status.Consensus.BlockHistory.Ok = true
75+
status.Consensus.BlockHistory.LatestVersion = uint64(blockstore.Height())
76+
status.Consensus.BlockHistory.LastRetainedVersion = uint64(blockstore.Base())
77+
}()
78+
79+
for _, rt := range runtimes {
80+
var rtStatus RuntimeStatus
81+
82+
func() {
83+
ndb, err := openRuntimeStateDB(dataDir, rt)
84+
if err != nil {
85+
logger.Error("failed to open runtime state DB", "err", err)
86+
return
87+
}
88+
defer ndb.Close()
89+
rtStatus.StateDB.LatestVersion, rtStatus.StateDB.Ok = ndb.GetLatestVersion()
90+
rtStatus.StateDB.LastRetainedVersion = ndb.GetEarliestVersion()
91+
}()
92+
93+
func() {
94+
history, err := openRuntimeLightHistory(dataDir, rt)
95+
if err != nil {
96+
logger.Error("failed to open light history", "err", err)
97+
return
98+
}
99+
defer history.Close()
100+
101+
latest, err := history.GetCommittedBlock(ctx, roothash.RoundLatest)
102+
if err != nil {
103+
logger.Error("failed to get latest light history block", "err", err)
104+
return
105+
}
106+
rtStatus.LightHistory.LatestVersion = latest.Header.Round
107+
earliest, err := history.GetEarliestBlock(ctx)
108+
if err != nil {
109+
logger.Error("failed to get earliest light history block", "err", err)
110+
return
111+
}
112+
rtStatus.LightHistory.LastRetainedVersion = earliest.Header.Round
113+
rtStatus.LightHistory.Ok = true
114+
}()
115+
116+
status.Runtimes[rt] = rtStatus
117+
}
118+
119+
return status
120+
}
121+
122+
func newInspectCmd() *cobra.Command {
123+
var outputFormat string
124+
125+
cmd := &cobra.Command{
126+
Use: "inspect",
127+
Short: "inspect storage",
128+
PreRunE: func(_ *cobra.Command, args []string) error {
129+
if err := cmdCommon.Init(); err != nil {
130+
cmdCommon.EarlyLogAndExit(err)
131+
}
132+
133+
running, err := cmdCommon.IsNodeRunning()
134+
if err != nil {
135+
return fmt.Errorf("failed to ensure the node is not running: %w", err)
136+
}
137+
if running {
138+
return fmt.Errorf("node is running")
139+
}
140+
141+
return nil
142+
},
143+
RunE: func(cmd *cobra.Command, args []string) error {
144+
runtimes, err := registry.GetConfiguredRuntimeIDs()
145+
if err != nil {
146+
return fmt.Errorf("failed to get configured runtimes: %w", err)
147+
}
148+
status := Inspect(cmd.Context(), cmdCommon.DataDir(), runtimes)
149+
150+
switch outputFormat {
151+
case "json":
152+
prettyStatus, err := cmdCommon.PrettyJSONMarshal(status)
153+
if err != nil {
154+
return fmt.Errorf("failed to marshal status as JSON: %w", err)
155+
}
156+
fmt.Println(string(prettyStatus))
157+
return nil
158+
case "text", "":
159+
// Fall through to text output.
160+
default:
161+
return fmt.Errorf("unsupported output format: %s (supported: text, json)", outputFormat)
162+
}
163+
164+
fmt.Println("Consensus:")
165+
fmt.Println(" State DB:")
166+
fmt.Println(" Ok: ", status.Consensus.StateDB.Ok)
167+
fmt.Println(" Latest height: ", status.Consensus.StateDB.LatestVersion)
168+
fmt.Println(" Last retained height: ", status.Consensus.StateDB.LastRetainedVersion)
169+
fmt.Println(" Block history:")
170+
fmt.Println(" Ok: ", status.Consensus.BlockHistory.Ok)
171+
fmt.Println(" Latest height: ", status.Consensus.BlockHistory.LatestVersion)
172+
fmt.Println(" Last retained height: ", status.Consensus.BlockHistory.LastRetainedVersion)
173+
174+
if len(status.Runtimes) == 0 {
175+
return nil
176+
}
177+
178+
fmt.Println("Runtimes:")
179+
for rt, rtStatus := range status.Runtimes {
180+
fmt.Println(" ", rt)
181+
fmt.Println(" State DB:")
182+
fmt.Println(" Ok: ", rtStatus.StateDB.Ok)
183+
fmt.Println(" Latest round: ", rtStatus.StateDB.LatestVersion)
184+
fmt.Println(" Last retained round: ", rtStatus.StateDB.LastRetainedVersion)
185+
fmt.Println(" Light History:")
186+
fmt.Println(" Ok: ", rtStatus.LightHistory.Ok)
187+
fmt.Println(" Latest round: ", rtStatus.LightHistory.LatestVersion)
188+
fmt.Println(" Last retained round: ", rtStatus.LightHistory.LastRetainedVersion)
189+
}
190+
191+
return nil
192+
},
193+
}
194+
195+
cmd.Flags().StringVar(&outputFormat, "output", "text", "output format (text, json)")
196+
197+
return cmd
198+
}

0 commit comments

Comments
 (0)