diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go new file mode 100644 index 000000000..2c84184bf --- /dev/null +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -0,0 +1,243 @@ +package main + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "os" + "path" + + "github.com/spf13/cobra" + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + cmn "github.com/tendermint/tmlibs/common" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/stake" + + gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" +) + +func runHackCmd(cmd *cobra.Command, args []string) error { + + if len(args) != 1 { + return fmt.Errorf("Expected 1 arg") + } + + // ".gaiad" + dataDir := args[0] + dataDir = path.Join(dataDir, "data") + + // load the app + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + db, err := dbm.NewGoLevelDB("gaia", dataDir) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + app := NewGaiaApp(logger, db) + + // print some info + id := app.LastCommitID() + lastBlockHeight := app.LastBlockHeight() + fmt.Println("ID", id) + fmt.Println("LastBlockHeight", lastBlockHeight) + + //---------------------------------------------------- + // XXX: start hacking! + //---------------------------------------------------- + // eg. gaia-6001 testnet bug + // We paniced when iterating through the "bypower" keys. + // The following powerKey was there, but the corresponding "trouble" validator did not exist. + // So here we do a binary search on the past states to find when the powerKey first showed up ... + + // owner of the validator the bonds, gets revoked, later unbonds, and then later is still found in the bypower store + trouble := hexToBytes("D3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") + // this is his "bypower" key + powerKey := hexToBytes("05303030303030303030303033FFFFFFFFFFFF4C0C0000FFFED3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") + + topHeight := lastBlockHeight + bottomHeight := int64(0) + checkHeight := topHeight + for { + // load the given version of the state + err = app.LoadVersion(checkHeight, app.keyMain) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + ctx := app.NewContext(true, abci.Header{}) + + // check for the powerkey and the validator from the store + store := ctx.KVStore(app.keyStake) + res := store.Get(powerKey) + val, _ := app.stakeKeeper.GetValidator(ctx, trouble) + fmt.Println("checking height", checkHeight, res, val) + if res == nil { + bottomHeight = checkHeight + } else { + topHeight = checkHeight + } + checkHeight = (topHeight + bottomHeight) / 2 + } +} + +func base64ToPub(b64 string) crypto.PubKeyEd25519 { + data, _ := base64.StdEncoding.DecodeString(b64) + var pubKey crypto.PubKeyEd25519 + copy(pubKey[:], data) + return pubKey + +} + +func hexToBytes(h string) []byte { + trouble, _ := hex.DecodeString(h) + return trouble + +} + +//-------------------------------------------------------------------------------- +// NOTE: This is all copied from gaia/app/app.go +// so we can access internal fields! + +const ( + appName = "GaiaApp" +) + +// default home directories for expected binaries +var ( + DefaultCLIHome = os.ExpandEnv("$HOME/.gaiacli") + DefaultNodeHome = os.ExpandEnv("$HOME/.gaiad") +) + +// Extended ABCI application +type GaiaApp struct { + *bam.BaseApp + cdc *wire.Codec + + // keys to access the substores + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyIBC *sdk.KVStoreKey + keyStake *sdk.KVStoreKey + keySlashing *sdk.KVStoreKey + + // Manage getting and setting accounts + accountMapper auth.AccountMapper + feeCollectionKeeper auth.FeeCollectionKeeper + coinKeeper bank.Keeper + ibcMapper ibc.Mapper + stakeKeeper stake.Keeper + slashingKeeper slashing.Keeper +} + +func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { + cdc := MakeCodec() + + // create your application object + var app = &GaiaApp{ + BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + cdc: cdc, + keyMain: sdk.NewKVStoreKey("main"), + keyAccount: sdk.NewKVStoreKey("acc"), + keyIBC: sdk.NewKVStoreKey("ibc"), + keyStake: sdk.NewKVStoreKey("stake"), + keySlashing: sdk.NewKVStoreKey("slashing"), + } + + // define the accountMapper + app.accountMapper = auth.NewAccountMapper( + app.cdc, + app.keyAccount, // target store + &auth.BaseAccount{}, // prototype + ) + + // add handlers + app.coinKeeper = bank.NewKeeper(app.accountMapper) + app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) + app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace)) + + // register message routes + app.Router(). + AddRoute("bank", bank.NewHandler(app.coinKeeper)). + AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)). + AddRoute("stake", stake.NewHandler(app.stakeKeeper)) + + // initialize BaseApp + app.SetInitChainer(app.initChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing) + err := app.LoadLatestVersion(app.keyMain) + if err != nil { + cmn.Exit(err.Error()) + } + + return app +} + +// custom tx codec +func MakeCodec() *wire.Codec { + var cdc = wire.NewCodec() + ibc.RegisterWire(cdc) + bank.RegisterWire(cdc) + stake.RegisterWire(cdc) + slashing.RegisterWire(cdc) + auth.RegisterWire(cdc) + sdk.RegisterWire(cdc) + wire.RegisterCrypto(cdc) + return cdc +} + +// application updates every end block +func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } +} + +// application updates every end block +func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } +} + +// custom logic for gaia initialization +func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + // TODO is this now the whole genesis file? + + var genesisState gaia.GenesisState + err := app.cdc.UnmarshalJSON(stateJSON, &genesisState) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + + // load the accounts + for _, gacc := range genesisState.Accounts { + acc := gacc.ToAccount() + app.accountMapper.SetAccount(ctx, acc) + } + + // load the initial stake information + stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + return abci.ResponseInitChain{} + +} diff --git a/cmd/gaia/cmd/gaiadebug/main.go b/cmd/gaia/cmd/gaiadebug/main.go index ed5344bdd..914746dbd 100644 --- a/cmd/gaia/cmd/gaiadebug/main.go +++ b/cmd/gaia/cmd/gaiadebug/main.go @@ -17,6 +17,7 @@ import ( func init() { rootCmd.AddCommand(txCmd) rootCmd.AddCommand(pubkeyCmd) + rootCmd.AddCommand(hackCmd) } var rootCmd = &cobra.Command{ @@ -37,6 +38,12 @@ var pubkeyCmd = &cobra.Command{ RunE: runPubKeyCmd, } +var hackCmd = &cobra.Command{ + Use: "hack", + Short: "Boilerplate to Hack on an existing state by scripting some Go...", + RunE: runHackCmd, +} + func runPubKeyCmd(cmd *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("Expected single arg")