diff --git a/app/ethermint.go b/app/ethermint.go index e98536f8..ad9bbd56 100644 --- a/app/ethermint.go +++ b/app/ethermint.go @@ -5,12 +5,19 @@ import ( 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/gov" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/pkg/errors" "github.com/cosmos/ethermint/handlers" "github.com/cosmos/ethermint/types" ethparams "github.com/ethereum/go-ethereum/params" + abci "github.com/tendermint/tendermint/abci/types" tmcmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" tmlog "github.com/tendermint/tendermint/libs/log" @@ -27,52 +34,146 @@ type ( EthermintApp struct { *bam.BaseApp - codec *wire.Codec - sealed bool + codec *wire.Codec - accountKey *sdk.KVStoreKey - accountMapper auth.AccountMapper - // TODO: keys, stores, mappers, and keepers + accountKey *sdk.KVStoreKey + mainKey *sdk.KVStoreKey + stakeKey *sdk.KVStoreKey + slashingKey *sdk.KVStoreKey + govKey *sdk.KVStoreKey + feeCollKey *sdk.KVStoreKey + paramsKey *sdk.KVStoreKey + tParamsKey *sdk.TransientStoreKey + + accountMapper auth.AccountMapper + feeCollKeeper auth.FeeCollectionKeeper + coinKeeper bank.Keeper + stakeKeeper stake.Keeper + slashingKeeper slashing.Keeper + govKeeper gov.Keeper + paramsKeeper params.Keeper } - - // Options is a function signature that provides the ability to modify - // options of an EthermintApp during initialization. - Options func(*EthermintApp) ) // NewEthermintApp returns a reference to a new initialized Ethermint // application. -func NewEthermintApp(logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, opts ...Options, +func NewEthermintApp( + logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, baseAppOptions ...func(*bam.BaseApp), ) *EthermintApp { codec := CreateCodec() app := &EthermintApp{ - BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec)), - codec: codec, - accountKey: sdk.NewKVStoreKey("accounts"), + BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec), baseAppOptions...), + codec: codec, + accountKey: sdk.NewKVStoreKey("acc"), + mainKey: sdk.NewKVStoreKey("main"), + stakeKey: sdk.NewKVStoreKey("stake"), + slashingKey: sdk.NewKVStoreKey("slashing"), + govKey: sdk.NewKVStoreKey("gov"), + feeCollKey: sdk.NewKVStoreKey("fee"), + paramsKey: sdk.NewKVStoreKey("params"), + tParamsKey: sdk.NewTransientStoreKey("transient_params"), } + + // set application keepers and mappers app.accountMapper = auth.NewAccountMapper(codec, app.accountKey, auth.ProtoBaseAccount) + app.coinKeeper = bank.NewKeeper(app.accountMapper) + app.paramsKeeper = params.NewKeeper(app.codec, app.paramsKey) + app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.codec, app.feeCollKey) + app.stakeKeeper = stake.NewKeeper( + app.codec, app.stakeKey, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace), + ) + app.govKeeper = gov.NewKeeper( + app.codec, app.govKey, app.paramsKeeper.Setter(), app.coinKeeper, + app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace), + ) + app.slashingKeeper = slashing.NewKeeper( + app.codec, app.slashingKey, app.stakeKeeper, + app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace), + ) - app.SetAnteHandler(handlers.AnteHandler(app.accountMapper)) - app.MountStoresIAVL(app.accountKey) + // register message handlers + app.Router(). + // TODO: Do we need to mount bank and IBC handlers? Should be handled + // directly in the EVM. + AddRoute("stake", stake.NewHandler(app.stakeKeeper)). + AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)). + AddRoute("gov", gov.NewHandler(app.govKeeper)) - for _, opt := range opts { - opt(app) - } + // initialize the underlying ABCI BaseApp + app.SetInitChainer(app.initChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.SetAnteHandler(handlers.AnteHandler(app.accountMapper, app.feeCollKeeper)) - err := app.LoadLatestVersion(app.accountKey) - if err != nil { + app.MountStoresIAVL( + app.mainKey, app.accountKey, app.stakeKey, app.slashingKey, + app.govKey, app.feeCollKey, app.paramsKey, + ) + app.MountStore(app.tParamsKey, sdk.StoreTypeTransient) + + if err := app.LoadLatestVersion(app.accountKey); err != nil { tmcmn.Exit(err.Error()) } - app.seal() + app.BaseApp.Seal() return app } -// seal seals the Ethermint application and prohibits any future modifications -// that change critical components. -func (app *EthermintApp) seal() { - app.sealed = true +// BeginBlocker signals the beginning of a block. It performs application +// updates on the start of every block. +func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } +} + +// EndBlocker signals the end of a block. It performs application updates on +// the end of every block. +func (app *EthermintApp) EndBlocker(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { + tags := gov.EndBlocker(ctx, app.govKeeper) + validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + + app.slashingKeeper.AddValidators(ctx, validatorUpdates) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + Tags: tags, + } +} + +// initChainer initializes the application blockchain with validators and other +// state data from TendermintCore. +func (app *EthermintApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + var genesisState GenesisState + stateJSON := req.AppStateBytes + + err := app.codec.UnmarshalJSON(stateJSON, &genesisState) + if err != nil { + panic(errors.Wrap(err, "failed to parse application genesis state")) + } + + // load the genesis accounts + for _, genAcc := range genesisState.Accounts { + acc := genAcc.ToAccount() + acc.AccountNumber = app.accountMapper.GetNextAccountNumber(ctx) + app.accountMapper.SetAccount(ctx, acc) + } + + // load the genesis stake information + validators, err := stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + if err != nil { + panic(errors.Wrap(err, "failed to initialize genesis validators")) + } + + slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.StakeData) + gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) + + return abci.ResponseInitChain{ + Validators: validators, + } } // CreateCodec creates a new amino wire codec and registers all the necessary @@ -80,7 +181,12 @@ func (app *EthermintApp) seal() { func CreateCodec() *wire.Codec { codec := wire.NewCodec() - // Register other modules, types, and messages... types.RegisterWire(codec) + auth.RegisterWire(codec) + gov.RegisterWire(codec) + slashing.RegisterWire(codec) + stake.RegisterWire(codec) + wire.RegisterCrypto(codec) + return codec } diff --git a/app/genesis.go b/app/genesis.go new file mode 100644 index 00000000..972ac8ca --- /dev/null +++ b/app/genesis.go @@ -0,0 +1,47 @@ +package app + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/ethermint/types" +) + +type ( + // GenesisState defines the application's genesis state. It contains all the + // information required and accounts to initialize the blockchain. + GenesisState struct { + Accounts []GenesisAccount `json:"accounts"` + StakeData stake.GenesisState `json:"stake"` + GovData gov.GenesisState `json:"gov"` + } + + // GenesisAccount defines an account to be initialized in the genesis state. + GenesisAccount struct { + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + Code []byte `json:"code,omitempty"` + Storage types.Storage `json:"storage,omitempty"` + } +) + +// NewGenesisAccount returns a reference to a new initialized genesis account. +func NewGenesisAccount(acc *types.Account) GenesisAccount { + return GenesisAccount{ + Address: acc.GetAddress(), + Coins: acc.GetCoins(), + Code: acc.Code, + Storage: acc.Storage, + } +} + +// ToAccount converts a genesis account to an initialized Ethermint account. +func (ga *GenesisAccount) ToAccount() (acc *types.Account) { + base := auth.BaseAccount{ + Address: ga.Address, + Coins: ga.Coins.Sort(), + } + + return types.NewAccount(base, ga.Code, ga.Storage) +} diff --git a/handlers/ante.go b/handlers/ante.go index a1b69d24..473e1abb 100644 --- a/handlers/ante.go +++ b/handlers/ante.go @@ -30,7 +30,7 @@ type internalAnteHandler func( // transaction to an internal ante handler for performing transaction-level // processing (e.g. fee payment, signature verification) before being passed // onto it's respective handler. -func AnteHandler(am auth.AccountMapper) sdk.AnteHandler { +func AnteHandler(am auth.AccountMapper, _ auth.FeeCollectionKeeper) sdk.AnteHandler { return func(sdkCtx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { var ( gasLimit int64 diff --git a/types/account.go b/types/account.go new file mode 100644 index 00000000..b82808c4 --- /dev/null +++ b/types/account.go @@ -0,0 +1,53 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + + ethcmn "github.com/ethereum/go-ethereum/common" +) + +var _ auth.Account = (*Account)(nil) + +type ( + // Storage defines account storage + Storage map[ethcmn.Hash]ethcmn.Hash + + // Account defines an auth.BaseAccount extension for Ethermint. It is + // compatible with the auth.AccountMapper. + Account struct { + auth.BaseAccount + + Code []byte + Storage Storage + } +) + +// NewAccount returns a reference to a new initialized account. +func NewAccount(base auth.BaseAccount, code []byte, storage Storage) *Account { + return &Account{ + BaseAccount: base, + Code: code, + Storage: storage, + } +} + +// GetAccountDecoder returns the auth.AccountDecoder function for the custom +// Account type. +func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { + return func(accBytes []byte) (auth.Account, error) { + if len(accBytes) == 0 { + return nil, sdk.ErrTxDecode("account bytes are empty") + } + + acc := new(Account) + + err := cdc.UnmarshalBinaryBare(accBytes, &acc) + if err != nil { + return nil, sdk.ErrTxDecode("failed to decode account bytes") + } + + return acc, err + } +} diff --git a/types/tx.go b/types/tx.go index d377779b..3c3b7dd2 100644 --- a/types/tx.go +++ b/types/tx.go @@ -17,6 +17,8 @@ import ( "github.com/pkg/errors" ) +var _ sdk.Tx = (*Transaction)(nil) + const ( // TypeTxEthereum reflects an Ethereum Transaction type. TypeTxEthereum = "Ethereum" diff --git a/types/wire.go b/types/wire.go index b596a3db..23f69aeb 100644 --- a/types/wire.go +++ b/types/wire.go @@ -15,6 +15,7 @@ func init() { // codec. func RegisterWire(codec *wire.Codec) { sdk.RegisterWire(codec) + codec.RegisterConcrete(&Account{}, "types/Account", nil) codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil) codec.RegisterConcrete(TxData{}, "types/TxData", nil) codec.RegisterConcrete(Transaction{}, "types/Transaction", nil)