Skip to content

Commit

Permalink
Merge pull request #2921 from oasislabs/yawning/feature/2359
Browse files Browse the repository at this point in the history
go/oasis-node: Add the `debug dumpdb` command
  • Loading branch information
Yawning committed May 21, 2020
2 parents 3d5e7dc + 00c0ed3 commit a28a9cd
Show file tree
Hide file tree
Showing 29 changed files with 668 additions and 63 deletions.
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

0 comments on commit a28a9cd

Please sign in to comment.