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

go/oasis-node: Add the debug dumpdb command #2921

Merged
merged 4 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changelog/2359.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
go/oasis-node/cmd/debug: Add the `dumpdb` command

This command will attempt to extract the ABCI state from a combination
of a shutdown node's on-disk database and the genesis document currently
being used by the network, and will write the output as a JSON formatted
genesis document.

Some caveats:

- It is not guaranteed that the dumped output will be usable as an
actual genesis document without manual intervention.

- Only the state that would be exported via a normal dump from a running
node will be present in the dump.

- The epochtime base will be that of the original genesis document, and
not the most recent epoch (different from a genesis dump).
3 changes: 3 additions & 0 deletions go/consensus/tendermint/abci/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type ApplicationConfig struct {

// MemoryOnlyStorage forces in-memory storage to be used for the state storage.
MemoryOnlyStorage bool

// ReadOnlyStorage forces read-only access for the state storage.
ReadOnlyStorage bool
}

// TransactionAuthHandler is the interface for ABCI applications that handle
Expand Down
58 changes: 43 additions & 15 deletions go/consensus/tendermint/abci/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
Expand Down Expand Up @@ -359,16 +360,31 @@ func (s *applicationState) pruneWorker() {
}
}

func newApplicationState(ctx context.Context, cfg *ApplicationConfig) (*applicationState, error) {
// InitStateStorage initializes the internal ABCI state storage.
func InitStateStorage(ctx context.Context, cfg *ApplicationConfig) (storage.LocalBackend, storage.NodeDB, *storage.Root, error) {
baseDir := filepath.Join(cfg.DataDir, appStateDir)
if err := common.Mkdir(baseDir); err != nil {
return nil, fmt.Errorf("failed to create application state directory: %w", err)
switch cfg.ReadOnlyStorage {
case true:
// Note: I'm not sure what badger does when given a path that
// doesn't actually contain a database, when it's set to
// read-only. Hopefully it's something sensible.
fi, err := os.Lstat(baseDir)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to stat application state directory: %w", err)
}
if !fi.Mode().IsDir() {
return nil, nil, nil, fmt.Errorf("application state path is not a directory: %v", fi.Mode())
}
default:
if err := common.Mkdir(baseDir); err != nil {
return nil, nil, nil, fmt.Errorf("failed to create application state directory: %w", err)
}
}

switch cfg.StorageBackend {
case storageDB.BackendNameBadgerDB:
default:
return nil, fmt.Errorf("unsupported storage backend: %s", cfg.StorageBackend)
return nil, nil, nil, fmt.Errorf("unsupported storage backend: %s", cfg.StorageBackend)
}

db, err := storageDB.New(&storage.Config{
Expand All @@ -378,9 +394,10 @@ func newApplicationState(ctx context.Context, cfg *ApplicationConfig) (*applicat
DiscardWriteLogs: true,
NoFsync: true, // This is safe as Tendermint will replay on crash.
MemoryOnly: cfg.MemoryOnlyStorage,
ReadOnly: cfg.ReadOnlyStorage,
})
if err != nil {
return nil, err
return nil, nil, nil, err
}
ldb := db.(storage.LocalBackend)
ndb := ldb.NodeDB()
Expand All @@ -396,33 +413,46 @@ func newApplicationState(ctx context.Context, cfg *ApplicationConfig) (*applicat
// Figure out the latest version/hash if any, and use that as the block height/hash.
latestVersion, err := ndb.GetLatestVersion(ctx)
if err != nil {
return nil, err
return nil, nil, nil, err
}
roots, err := ndb.GetRootsForVersion(ctx, latestVersion)
if err != nil {
return nil, err
return nil, nil, nil, err
}
stateRoot := storage.Root{
stateRoot := &storage.Root{
Version: latestVersion,
}
switch len(roots) {
case 0:
// No roots -- empty database.
if latestVersion != 0 {
return nil, fmt.Errorf("state: no roots at non-zero height, corrupted database?")
return nil, nil, nil, fmt.Errorf("state: no roots at non-zero height, corrupted database?")
}
stateRoot.Hash.Empty()
case 1:
// Exactly one root -- the usual case.
stateRoot.Hash = roots[0]
default:
// More roots -- should not happen for our use case.
return nil, fmt.Errorf("state: more than one root, corrupted database?")
return nil, nil, nil, fmt.Errorf("state: more than one root, corrupted database?")
}

ok = true

return ldb, ndb, stateRoot, nil
}

func newApplicationState(ctx context.Context, cfg *ApplicationConfig) (*applicationState, error) {
// Initialize the state storage.
ldb, ndb, stateRoot, err := InitStateStorage(ctx, cfg)
if err != nil {
return nil, err
}
latestVersion := stateRoot.Version

// Use the node database directly to avoid going through the syncer interface.
deliverTxTree := mkvs.NewWithRoot(nil, ndb, stateRoot, mkvs.WithoutWriteLog())
checkTxTree := mkvs.NewWithRoot(nil, ndb, stateRoot, mkvs.WithoutWriteLog())
deliverTxTree := mkvs.NewWithRoot(nil, ndb, *stateRoot, mkvs.WithoutWriteLog())
checkTxTree := mkvs.NewWithRoot(nil, ndb, *stateRoot, mkvs.WithoutWriteLog())

// Initialize the state pruner.
statePruner, err := newStatePruner(&cfg.Pruning, ndb, latestVersion)
Expand All @@ -443,7 +473,7 @@ func newApplicationState(ctx context.Context, cfg *ApplicationConfig) (*applicat
cancelCtx: cancelCtx,
deliverTxTree: deliverTxTree,
checkTxTree: checkTxTree,
stateRoot: stateRoot,
stateRoot: *stateRoot,
storage: ldb,
statePruner: statePruner,
prunerClosedCh: make(chan struct{}),
Expand All @@ -462,8 +492,6 @@ func newApplicationState(ctx context.Context, cfg *ApplicationConfig) (*applicat
}
}

ok = true

go s.metricsWorker()
go s.pruneWorker()

Expand Down
2 changes: 1 addition & 1 deletion go/consensus/tendermint/abci/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type ImmutableState struct {
}

// NewImmutableState creates a new immutable consensus backend state wrapper.
func NewImmutableState(ctx context.Context, state api.ApplicationState, version int64) (*ImmutableState, error) {
func NewImmutableState(ctx context.Context, state api.ApplicationQueryState, version int64) (*ImmutableState, error) {
is, err := api.NewImmutableState(ctx, state, version)
if err != nil {
return nil, err
Expand Down
24 changes: 15 additions & 9 deletions go/consensus/tendermint/api/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ var (

// ApplicationState is the overall past, present and future state of all multiplexed applications.
type ApplicationState interface {
// Storage returns the storage backend.
Storage() storage.LocalBackend

// BlockHeight returns the last committed block height.
BlockHeight() int64
ApplicationQueryState

// BlockHash returns the last committed block hash.
BlockHash() []byte
Expand All @@ -49,9 +45,6 @@ type ApplicationState interface {
// GetBaseEpoch returns the base epoch.
GetBaseEpoch() (epochtime.EpochTime, error)

// GetEpoch returns epoch at block height.
GetEpoch(ctx context.Context, blockHeight int64) (epochtime.EpochTime, error)

// GetCurrentEpoch returns the epoch at the current block height.
GetCurrentEpoch(ctx context.Context) (epochtime.EpochTime, error)

Expand All @@ -69,6 +62,19 @@ type ApplicationState interface {
NewContext(mode ContextMode, now time.Time) *Context
}

// ApplicationQueryState is minimum methods required to service
// ApplicationState queries.
type ApplicationQueryState interface {
// Storage returns the storage backend.
Storage() storage.LocalBackend

// BlockHeight returns the last committed block height.
BlockHeight() int64

// GetEpoch returns epoch at block height.
GetEpoch(ctx context.Context, blockHeight int64) (epochtime.EpochTime, error)
}

// MockApplicationStateConfig is the configuration for the mock application state.
type MockApplicationStateConfig struct {
BlockHeight int64
Expand Down Expand Up @@ -203,7 +209,7 @@ func (s *ImmutableState) Close() {
}

// NewImmutableState creates a new immutable state wrapper.
func NewImmutableState(ctx context.Context, state ApplicationState, version int64) (*ImmutableState, error) {
func NewImmutableState(ctx context.Context, state ApplicationQueryState, version int64) (*ImmutableState, error) {
if state == nil {
return nil, ErrNoState
}
Expand Down
13 changes: 10 additions & 3 deletions go/consensus/tendermint/apps/beacon/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

beacon "github.com/oasislabs/oasis-core/go/beacon/api"
abciAPI "github.com/oasislabs/oasis-core/go/consensus/tendermint/api"
beaconState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/beacon/state"
)

Expand All @@ -15,12 +16,12 @@ type Query interface {

// QueryFactory is the beacon query factory.
type QueryFactory struct {
app *beaconApplication
state abciAPI.ApplicationQueryState
}

// QueryAt returns the beacon query interface for a specific height.
func (sf *QueryFactory) QueryAt(ctx context.Context, height int64) (Query, error) {
state, err := beaconState.NewImmutableState(ctx, sf.app.state, height)
state, err := beaconState.NewImmutableState(ctx, sf.state, height)
if err != nil {
return nil, err
}
Expand All @@ -36,5 +37,11 @@ func (bq *beaconQuerier) Beacon(ctx context.Context) ([]byte, error) {
}

func (app *beaconApplication) QueryFactory() interface{} {
return &QueryFactory{app}
return &QueryFactory{app.state}
}

// NewQueryFactory returns a new QueryFactory backed by the given state
// instance.
func NewQueryFactory(state abciAPI.ApplicationQueryState) *QueryFactory {
return &QueryFactory{state}
}
2 changes: 1 addition & 1 deletion go/consensus/tendermint/apps/beacon/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type ImmutableState struct {
is *abciAPI.ImmutableState
}

func NewImmutableState(ctx context.Context, state abciAPI.ApplicationState, version int64) (*ImmutableState, error) {
func NewImmutableState(ctx context.Context, state abciAPI.ApplicationQueryState, version int64) (*ImmutableState, error) {
is, err := abciAPI.NewImmutableState(ctx, state, version)
if err != nil {
return nil, err
Expand Down
13 changes: 10 additions & 3 deletions go/consensus/tendermint/apps/epochtime_mock/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package epochtimemock
import (
"context"

abciAPI "github.com/oasislabs/oasis-core/go/consensus/tendermint/api"
epochtime "github.com/oasislabs/oasis-core/go/epochtime/api"
)

Expand All @@ -13,12 +14,12 @@ type Query interface {

// QueryFactory is the mock epochtime query factory.
type QueryFactory struct {
app *epochTimeMockApplication
state abciAPI.ApplicationQueryState
}

// QueryAt returns the mock epochtime query interface for a specific height.
func (sf *QueryFactory) QueryAt(ctx context.Context, height int64) (Query, error) {
state, err := newImmutableState(ctx, sf.app.state, height)
state, err := newImmutableState(ctx, sf.state, height)
if err != nil {
return nil, err
}
Expand All @@ -34,5 +35,11 @@ func (eq *epochtimeMockQuerier) Epoch(ctx context.Context) (epochtime.EpochTime,
}

func (app *epochTimeMockApplication) QueryFactory() interface{} {
return &QueryFactory{app}
return &QueryFactory{app.state}
}

// NewQueryFactory returns a new QueryFactory backed by the given state
// instance.
func NewQueryFactory(state abciAPI.ApplicationQueryState) *QueryFactory {
return &QueryFactory{state}
}
2 changes: 1 addition & 1 deletion go/consensus/tendermint/apps/epochtime_mock/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (s *immutableState) getFutureEpoch(ctx context.Context) (*mockEpochTimeStat
return &state, nil
}

func newImmutableState(ctx context.Context, state abciAPI.ApplicationState, version int64) (*immutableState, error) {
func newImmutableState(ctx context.Context, state abciAPI.ApplicationQueryState, version int64) (*immutableState, error) {
is, err := abciAPI.NewImmutableState(ctx, state, version)
if err != nil {
return nil, err
Expand Down
13 changes: 10 additions & 3 deletions go/consensus/tendermint/apps/keymanager/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/oasislabs/oasis-core/go/common"
abciAPI "github.com/oasislabs/oasis-core/go/consensus/tendermint/api"
keymanagerState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/keymanager/state"
keymanager "github.com/oasislabs/oasis-core/go/keymanager/api"
)
Expand All @@ -17,12 +18,12 @@ type Query interface {

// QueryFactory is the key manager query factory.
type QueryFactory struct {
app *keymanagerApplication
state abciAPI.ApplicationQueryState
}

// QueryAt returns the key manager query interface for a specific height.
func (sf *QueryFactory) QueryAt(ctx context.Context, height int64) (Query, error) {
state, err := keymanagerState.NewImmutableState(ctx, sf.app.state, height)
state, err := keymanagerState.NewImmutableState(ctx, sf.state, height)
if err != nil {
return nil, err
}
Expand All @@ -42,5 +43,11 @@ func (kq *keymanagerQuerier) Statuses(ctx context.Context) ([]*keymanager.Status
}

func (app *keymanagerApplication) QueryFactory() interface{} {
return &QueryFactory{app}
return &QueryFactory{app.state}
}

// NewQueryFactory returns a new QueryFactory backed by the given state
// instance.
func NewQueryFactory(state abciAPI.ApplicationQueryState) *QueryFactory {
return &QueryFactory{state}
}
2 changes: 1 addition & 1 deletion go/consensus/tendermint/apps/keymanager/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (st *ImmutableState) Status(ctx context.Context, id common.Namespace) (*api
return &status, nil
}

func NewImmutableState(ctx context.Context, state abciAPI.ApplicationState, version int64) (*ImmutableState, error) {
func NewImmutableState(ctx context.Context, state abciAPI.ApplicationQueryState, version int64) (*ImmutableState, error) {
is, err := abciAPI.NewImmutableState(ctx, state, version)
if err != nil {
return nil, err
Expand Down
Loading