diff --git a/CHANGELOG.md b/CHANGELOG.md index 3007effa..c89ba0ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,3 +40,13 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements * (x/evm) [\#181](https://github.com/ChainSafe/ethermint/issues/181) Updated EVM module to the recommended module structure. [@fedekunze](https://github.com/fedekunze) +* (app) [\#188](https://github.com/ChainSafe/ethermint/issues/186) Misc cleanup [@fedekunze](https://github.com/fedekunze): + * (`x/evm`) Rename `EthereumTxMsg` --> `MsgEthereumTx` and `EmintMsg` --> `MsgEthermint` for consistency with SDK standards + * Updated integration and unit tests to use `EthermintApp` as testing suite + * Use expected keeper interface for `AccountKeeper` + * Replaced `count` type in keeper with `int` + * Add SDK events for transactions +* [\#236](https://github.com/ChainSafe/ethermint/pull/236) Changes from upgrade [@fedekunze](https://github.com/fedekunze) + * (app/ante) Moved `AnteHandler` implementation to `app/ante` + * (keys) Marked `ExportEthKeyCommand` as **UNSAFE** + * (x/evm) Moved `BeginBlock` and `EndBlock` to `x/evm/abci.go` diff --git a/app/ante.go b/app/ante/ante.go similarity index 87% rename from app/ante.go rename to app/ante/ante.go index 3f042c61..74015725 100644 --- a/app/ante.go +++ b/app/ante/ante.go @@ -1,4 +1,4 @@ -package app +package ante import ( "fmt" @@ -46,15 +46,15 @@ func NewAnteHandler(ak auth.AccountKeeper, sk types.SupplyKeeper) sdk.AnteHandle ante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewValidateSigCountDecorator(ak), ante.NewDeductFeeDecorator(ak, sk), - ante.NewSigGasConsumeDecorator(ak, consumeSigGas), + ante.NewSigGasConsumeDecorator(ak, sigGasConsumer), ante.NewSigVerificationDecorator(ak), ante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator ) return stdAnte(ctx, tx, sim) - case *evmtypes.EthereumTxMsg: - return ethAnteHandler(ctx, ak, sk, castTx, sim) + case evmtypes.MsgEthereumTx: + return ethAnteHandler(ctx, ak, sk, &castTx, sim) default: return ctx, sdk.ErrInternal(fmt.Sprintf("transaction type invalid: %T", tx)) @@ -62,7 +62,9 @@ func NewAnteHandler(ak auth.AccountKeeper, sk types.SupplyKeeper) sdk.AnteHandle } } -func consumeSigGas( +// sigGasConsumer overrides the DefaultSigVerificationGasConsumer from the x/auth +// module on the SDK. It doesn't allow ed25519 nor multisig thresholds. +func sigGasConsumer( meter sdk.GasMeter, sig []byte, pubkey tmcrypto.PubKey, params types.Params, ) error { switch pubkey.(type) { @@ -89,7 +91,7 @@ func consumeSigGas( // prevent spam and DoS attacks. func ethAnteHandler( ctx sdk.Context, ak auth.AccountKeeper, sk types.SupplyKeeper, - ethTxMsg *evmtypes.EthereumTxMsg, sim bool, + ethTxMsg *evmtypes.MsgEthereumTx, sim bool, ) (newCtx sdk.Context, err error) { var senderAddr sdk.AccAddress @@ -120,7 +122,9 @@ func ethAnteHandler( if r := recover(); r != nil { switch rType := r.(type) { case sdk.ErrorOutOfGas: - log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) + log := fmt.Sprintf("out of gas in location: %v; gasUsed: %d", + rType.Descriptor, ctx.GasMeter().GasConsumed(), + ) err = sdk.ErrOutOfGas(log) default: panic(r) @@ -139,9 +143,9 @@ func ethAnteHandler( // Cost calculates the fees paid to validators based on gas limit and price cost := new(big.Int).Mul(ethTxMsg.Data.Price, new(big.Int).SetUint64(ethTxMsg.Data.GasLimit)) - feeAmt := sdk.Coins{ + feeAmt := sdk.NewCoins( sdk.NewCoin(emint.DenomDefault, sdk.NewIntFromBigInt(cost)), - } + ) err = auth.DeductFees(sk, ctx, senderAcc, feeAmt) if err != nil { @@ -166,7 +170,7 @@ func ethAnteHandler( } func validateEthTxCheckTx( - ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg, + ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx, ) (sdk.AccAddress, error) { // Validate sufficient fees have been provided that meet a minimum threshold // defined by the proposer (for mempool purposes during CheckTx). @@ -193,7 +197,7 @@ func validateEthTxCheckTx( } // Validates signature and returns sender address -func validateSignature(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg) (sdk.AccAddress, error) { +func validateSignature(ctx sdk.Context, ethTxMsg *evmtypes.MsgEthereumTx) (sdk.AccAddress, error) { // parse the chainID from a string to a base-10 integer chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) if !ok { @@ -214,7 +218,7 @@ func validateSignature(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg) (sdk.A // that the transaction uses before the transaction is executed. The gas is a // constant value of 21000 plus any cost inccured by additional bytes of data // supplied with the transaction. -func validateIntrinsicGas(ethTxMsg *evmtypes.EthereumTxMsg) error { +func validateIntrinsicGas(ethTxMsg *evmtypes.MsgEthereumTx) error { gas, err := ethcore.IntrinsicGas(ethTxMsg.Data.Payload, ethTxMsg.To() == nil, true) if err != nil { return sdk.ErrInternal(fmt.Sprintf("failed to compute intrinsic gas cost: %s", err)) @@ -222,7 +226,7 @@ func validateIntrinsicGas(ethTxMsg *evmtypes.EthereumTxMsg) error { if ethTxMsg.Data.GasLimit < gas { return sdk.ErrInternal( - fmt.Sprintf("intrinsic gas too low; %d < %d", ethTxMsg.Data.GasLimit, gas), + fmt.Sprintf("intrinsic gas too low: %d < %d", ethTxMsg.Data.GasLimit, gas), ) } @@ -232,7 +236,7 @@ func validateIntrinsicGas(ethTxMsg *evmtypes.EthereumTxMsg) error { // validateAccount validates the account nonce and that the account has enough // funds to cover the tx cost. func validateAccount( - ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg, signer sdk.AccAddress, + ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx, signer sdk.AccAddress, ) error { acc := ak.GetAccount(ctx, signer) @@ -241,8 +245,9 @@ func validateAccount( if ctx.BlockHeight() == 0 && acc.GetAccountNumber() != 0 { return sdk.ErrInternal( fmt.Sprintf( - "invalid account number for height zero; got %d, expected 0", acc.GetAccountNumber(), - )) + "invalid account number for height zero (got %d)", acc.GetAccountNumber(), + ), + ) } // Validate nonce is correct @@ -262,7 +267,7 @@ func validateAccount( } func checkNonce( - ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg, signer sdk.AccAddress, + ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx, signer sdk.AccAddress, ) error { acc := ak.GetAccount(ctx, signer) // Validate the transaction nonce is valid (equivalent to the sender account’s @@ -270,7 +275,8 @@ func checkNonce( seq := acc.GetSequence() if ethTxMsg.Data.AccountNonce != seq { return sdk.ErrInvalidSequence( - fmt.Sprintf("invalid nonce; got %d, expected %d", ethTxMsg.Data.AccountNonce, seq)) + fmt.Sprintf("invalid nonce; got %d, expected %d", ethTxMsg.Data.AccountNonce, seq), + ) } return nil @@ -281,7 +287,7 @@ func checkNonce( // proposer. // // NOTE: This should only be ran during a CheckTx mode. -func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg) error { +func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.MsgEthereumTx) error { // fee = GP * GL fee := sdk.NewDecCoinFromCoin(sdk.NewInt64Coin(emint.DenomDefault, ethTxMsg.Fee().Int64())) @@ -297,7 +303,9 @@ func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxM if !ctx.MinGasPrices().IsZero() && !allGTE { // reject the transaction that does not meet the minimum fee return sdk.ErrInsufficientFee( - fmt.Sprintf("insufficient fee, got: %q required: %q", fee, ctx.MinGasPrices()), + fmt.Sprintf( + "insufficient fee, got: %q required: %q", fee, ctx.MinGasPrices(), + ), ) } diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go new file mode 100644 index 00000000..4b3a084b --- /dev/null +++ b/app/ante/ante_test.go @@ -0,0 +1,301 @@ +package ante_test + +import ( + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + ethcmn "github.com/ethereum/go-ethereum/common" + + abci "github.com/tendermint/tendermint/abci/types" + tmcrypto "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ethermint/app" + "github.com/cosmos/ethermint/app/ante" + "github.com/cosmos/ethermint/types" + evmtypes "github.com/cosmos/ethermint/x/evm/types" +) + +func requireValidTx( + t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, sim bool, +) { + _, err := anteHandler(ctx, tx, sim) + require.NoError(t, err) +} + +func requireInvalidTx( + t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, + tx sdk.Tx, sim bool, +) { + _, err := anteHandler(ctx, tx, sim) + require.Error(t, err) +} + +func (suite *AnteTestSuite) TestValidEthTx() { + suite.ctx = suite.ctx.WithBlockHeight(1) + + addr1, priv1 := newTestAddrKey() + addr2, _ := newTestAddrKey() + + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + err := acc1.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) + + acc2 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr2) + err = acc2.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc2) + + // require a valid Ethereum tx to pass + to := ethcmn.BytesToAddress(addr2.Bytes()) + amt := big.NewInt(32) + gas := big.NewInt(20) + ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test")) + + tx := newTestEthTx(suite.ctx, ethMsg, priv1) + requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) +} + +func (suite *AnteTestSuite) TestValidTx() { + suite.ctx = suite.ctx.WithBlockHeight(1) + + addr1, priv1 := newTestAddrKey() + addr2, priv2 := newTestAddrKey() + + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + err := acc1.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) + + acc2 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr2) + err = acc2.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc2) + + // require a valid SDK tx to pass + fee := newTestStdFee() + msg1 := newTestMsg(addr1, addr2) + msgs := []sdk.Msg{msg1} + + privKeys := []tmcrypto.PrivKey{priv1, priv2} + accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} + accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()} + + tx := newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee) + + requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) +} + +func (suite *AnteTestSuite) TestSDKInvalidSigs() { + suite.ctx = suite.ctx.WithBlockHeight(1) + + addr1, priv1 := newTestAddrKey() + addr2, priv2 := newTestAddrKey() + addr3, priv3 := newTestAddrKey() + + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + err := acc1.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) + + acc2 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr2) + err = acc2.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc2) + + fee := newTestStdFee() + msg1 := newTestMsg(addr1, addr2) + + // require validation failure with no signers + msgs := []sdk.Msg{msg1} + + privKeys := []tmcrypto.PrivKey{} + accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} + accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()} + + tx := newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) + + // require validation failure with invalid number of signers + msgs = []sdk.Msg{msg1} + + privKeys = []tmcrypto.PrivKey{priv1} + accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} + accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence()} + + tx = newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) + + // require validation failure with an invalid signer + msg2 := newTestMsg(addr1, addr3) + msgs = []sdk.Msg{msg1, msg2} + + privKeys = []tmcrypto.PrivKey{priv1, priv2, priv3} + accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0} + accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence(), 0} + + tx = newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) +} + +func (suite *AnteTestSuite) TestSDKInvalidAcc() { + suite.ctx = suite.ctx.WithBlockHeight(1) + + addr1, priv1 := newTestAddrKey() + + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + err := acc1.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) + + fee := newTestStdFee() + msg1 := newTestMsg(addr1) + msgs := []sdk.Msg{msg1} + privKeys := []tmcrypto.PrivKey{priv1} + + // require validation failure with invalid account number + accNums := []uint64{1} + accSeqs := []uint64{acc1.GetSequence()} + + tx := newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) + + // require validation failure with invalid sequence (nonce) + accNums = []uint64{acc1.GetAccountNumber()} + accSeqs = []uint64{1} + + tx = newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) +} + +func (suite *AnteTestSuite) TestEthInvalidSig() { + suite.ctx = suite.ctx.WithBlockHeight(1) + + _, priv1 := newTestAddrKey() + addr2, _ := newTestAddrKey() + to := ethcmn.BytesToAddress(addr2.Bytes()) + amt := big.NewInt(32) + gas := big.NewInt(20) + ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test")) + + tx := newTestEthTx(suite.ctx, ethMsg, priv1) + ctx := suite.ctx.WithChainID("4") + requireInvalidTx(suite.T(), suite.anteHandler, ctx, tx, false) +} + +func (suite *AnteTestSuite) TestEthInvalidNonce() { + + suite.ctx = suite.ctx.WithBlockHeight(1) + + addr1, priv1 := newTestAddrKey() + addr2, _ := newTestAddrKey() + + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + err := acc.SetSequence(10) + suite.Require().NoError(err) + err = acc.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + // require a valid Ethereum tx to pass + to := ethcmn.BytesToAddress(addr2.Bytes()) + amt := big.NewInt(32) + gas := big.NewInt(20) + ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test")) + + tx := newTestEthTx(suite.ctx, ethMsg, priv1) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) +} + +func (suite *AnteTestSuite) TestEthInsufficientBalance() { + suite.ctx = suite.ctx.WithBlockHeight(1) + + addr1, priv1 := newTestAddrKey() + addr2, _ := newTestAddrKey() + + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + // require a valid Ethereum tx to pass + to := ethcmn.BytesToAddress(addr2.Bytes()) + amt := big.NewInt(32) + gas := big.NewInt(20) + ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test")) + + tx := newTestEthTx(suite.ctx, ethMsg, priv1) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) +} + +func (suite *AnteTestSuite) TestEthInvalidIntrinsicGas() { + suite.ctx = suite.ctx.WithBlockHeight(1) + + addr1, priv1 := newTestAddrKey() + addr2, _ := newTestAddrKey() + + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + err := acc.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + // require a valid Ethereum tx to pass + to := ethcmn.BytesToAddress(addr2.Bytes()) + amt := big.NewInt(32) + gas := big.NewInt(20) + gasLimit := uint64(1000) + ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, gasLimit, gas, []byte("test")) + + tx := newTestEthTx(suite.ctx, ethMsg, priv1) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) +} + +func (suite *AnteTestSuite) TestEthInvalidMempoolFees() { + // setup app with checkTx = true + suite.app = app.Setup(true) + suite.ctx = suite.app.BaseApp.NewContext(true, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()}) + suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.SupplyKeeper) + + suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewCoins(sdk.NewCoin(types.DenomDefault, sdk.NewInt(500000))))) + addr1, priv1 := newTestAddrKey() + addr2, _ := newTestAddrKey() + + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + err := acc.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + // require a valid Ethereum tx to pass + to := ethcmn.BytesToAddress(addr2.Bytes()) + amt := big.NewInt(32) + gas := big.NewInt(20) + ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("payload")) + + tx := newTestEthTx(suite.ctx, ethMsg, priv1) + requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) +} + +func (suite *AnteTestSuite) TestEthInvalidChainID() { + suite.ctx = suite.ctx.WithBlockHeight(1) + + addr1, priv1 := newTestAddrKey() + addr2, _ := newTestAddrKey() + + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + err := acc.SetCoins(newTestCoins()) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + // require a valid Ethereum tx to pass + to := ethcmn.BytesToAddress(addr2.Bytes()) + amt := big.NewInt(32) + gas := big.NewInt(20) + ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test")) + + tx := newTestEthTx(suite.ctx, ethMsg, priv1) + ctx := suite.ctx.WithChainID("bad-chain-id") + requireInvalidTx(suite.T(), suite.anteHandler, ctx, tx, false) +} diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go new file mode 100644 index 00000000..a41328f8 --- /dev/null +++ b/app/ante/utils_test.go @@ -0,0 +1,104 @@ +package ante_test + +import ( + "fmt" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + + "github.com/cosmos/ethermint/app" + ante "github.com/cosmos/ethermint/app/ante" + "github.com/cosmos/ethermint/crypto" + emint "github.com/cosmos/ethermint/types" + evmtypes "github.com/cosmos/ethermint/x/evm/types" + + ethcrypto "github.com/ethereum/go-ethereum/crypto" + + abci "github.com/tendermint/tendermint/abci/types" + tmcrypto "github.com/tendermint/tendermint/crypto" +) + +type AnteTestSuite struct { + suite.Suite + + ctx sdk.Context + app *app.EthermintApp + anteHandler sdk.AnteHandler +} + +func (suite *AnteTestSuite) SetupTest() { + checkTx := false + + suite.app = app.Setup(checkTx) + suite.app.Codec().RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) + + suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()}) + suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.SupplyKeeper) +} + +func TestAnteTestSuite(t *testing.T) { + suite.Run(t, new(AnteTestSuite)) +} + +func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { + return sdk.NewTestMsg(addrs...) +} + +func newTestCoins() sdk.Coins { + return sdk.NewCoins(sdk.NewInt64Coin(emint.DenomDefault, 500000000)) +} + +func newTestStdFee() auth.StdFee { + return auth.NewStdFee(220000, sdk.NewCoins(sdk.NewInt64Coin(emint.DenomDefault, 150))) +} + +// GenerateAddress generates an Ethereum address. +func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) { + privkey, _ := crypto.GenerateKey() + addr := ethcrypto.PubkeyToAddress(privkey.ToECDSA().PublicKey) + + return sdk.AccAddress(addr.Bytes()), privkey +} + +func newTestSDKTx( + ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey, + accNums []uint64, seqs []uint64, fee auth.StdFee, +) sdk.Tx { + + sigs := make([]auth.StdSignature, len(privs)) + for i, priv := range privs { + signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") + + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + + sigs[i] = auth.StdSignature{ + PubKey: priv.PubKey(), + Signature: sig, + } + } + + return auth.NewStdTx(msgs, fee, sigs, "") +} + +func newTestEthTx(ctx sdk.Context, msg evmtypes.MsgEthereumTx, priv tmcrypto.PrivKey) sdk.Tx { + chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) + if !ok { + panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID())) + } + + privkey, ok := priv.(crypto.PrivKeySecp256k1) + if !ok { + panic(fmt.Sprintf("invalid private key type: %T", priv)) + } + + msg.Sign(chainID, privkey.ToECDSA()) + return msg +} diff --git a/app/ante_test.go b/app/ante_test.go deleted file mode 100644 index 4b58f4b3..00000000 --- a/app/ante_test.go +++ /dev/null @@ -1,308 +0,0 @@ -package app - -import ( - "math/big" - "testing" - - ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - tmcrypto "github.com/tendermint/tendermint/crypto" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/ethermint/types" - evmtypes "github.com/cosmos/ethermint/x/evm/types" -) - -func requireValidTx( - t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, sim bool, -) { - _, err := anteHandler(ctx, tx, sim) - require.True(t, err == nil) -} - -func requireInvalidTx( - t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, - tx sdk.Tx, sim bool, code sdk.CodeType, -) { - - _, err := anteHandler(ctx, tx, sim) - // require.Equal(t, code, err, fmt.Sprintf("invalid result: %v", err)) - require.Error(t, err) - - if code == sdk.CodeOutOfGas { - _, ok := tx.(auth.StdTx) - require.True(t, ok, "tx must be in form auth.StdTx") - } -} - -func TestValidEthTx(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - addr1, priv1 := newTestAddrKey() - addr2, _ := newTestAddrKey() - - acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - // nolint:errcheck - acc1.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc1) - - acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2) - // nolint:errcheck - acc2.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc2) - - // require a valid Ethereum tx to pass - to := ethcmn.BytesToAddress(addr2.Bytes()) - amt := big.NewInt(32) - gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) - - tx := newTestEthTx(input.ctx, ethMsg, priv1) - requireValidTx(t, input.anteHandler, input.ctx, tx, false) -} - -func TestValidTx(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - addr1, priv1 := newTestAddrKey() - addr2, priv2 := newTestAddrKey() - - acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - // nolint:errcheck - acc1.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc1) - - acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2) - // nolint:errcheck - acc2.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc2) - - // require a valid SDK tx to pass - fee := newTestStdFee() - msg1 := newTestMsg(addr1, addr2) - msgs := []sdk.Msg{msg1} - - privKeys := []tmcrypto.PrivKey{priv1, priv2} - accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} - accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()} - - tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) - - requireValidTx(t, input.anteHandler, input.ctx, tx, false) -} - -func TestSDKInvalidSigs(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - addr1, priv1 := newTestAddrKey() - addr2, priv2 := newTestAddrKey() - addr3, priv3 := newTestAddrKey() - - acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - // nolint:errcheck - acc1.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc1) - - acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2) - // nolint:errcheck - acc2.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc2) - - fee := newTestStdFee() - msg1 := newTestMsg(addr1, addr2) - - // require validation failure with no signers - msgs := []sdk.Msg{msg1} - - privKeys := []tmcrypto.PrivKey{} - accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} - accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()} - - tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeNoSignatures) - - // require validation failure with invalid number of signers - msgs = []sdk.Msg{msg1} - - privKeys = []tmcrypto.PrivKey{priv1} - accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} - accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence()} - - tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized) - - // require validation failure with an invalid signer - msg2 := newTestMsg(addr1, addr3) - msgs = []sdk.Msg{msg1, msg2} - - privKeys = []tmcrypto.PrivKey{priv1, priv2, priv3} - accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0} - accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence(), 0} - - tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnknownAddress) -} - -func TestSDKInvalidAcc(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - addr1, priv1 := newTestAddrKey() - - acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - // nolint:errcheck - acc1.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc1) - - fee := newTestStdFee() - msg1 := newTestMsg(addr1) - msgs := []sdk.Msg{msg1} - privKeys := []tmcrypto.PrivKey{priv1} - - // require validation failure with invalid account number - accNums := []uint64{1} - accSeqs := []uint64{acc1.GetSequence()} - - tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized) - - // require validation failure with invalid sequence (nonce) - accNums = []uint64{acc1.GetAccountNumber()} - accSeqs = []uint64{1} - - tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized) -} - -func TestEthInvalidSig(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - _, priv1 := newTestAddrKey() - addr2, _ := newTestAddrKey() - to := ethcmn.BytesToAddress(addr2.Bytes()) - amt := big.NewInt(32) - gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) - - tx := newTestEthTx(input.ctx, ethMsg, priv1) - ctx := input.ctx.WithChainID("4") - requireInvalidTx(t, input.anteHandler, ctx, tx, false, sdk.CodeUnauthorized) -} - -func TestEthInvalidNonce(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - addr1, priv1 := newTestAddrKey() - addr2, _ := newTestAddrKey() - - acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - // nolint:errcheck - acc.SetCoins(newTestCoins()) - // nolint:errcheck - acc.SetSequence(10) - input.accKeeper.SetAccount(input.ctx, acc) - - // require a valid Ethereum tx to pass - to := ethcmn.BytesToAddress(addr2.Bytes()) - amt := big.NewInt(32) - gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) - - tx := newTestEthTx(input.ctx, ethMsg, priv1) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInvalidSequence) -} - -func TestEthInsufficientBalance(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - addr1, priv1 := newTestAddrKey() - addr2, _ := newTestAddrKey() - - acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - input.accKeeper.SetAccount(input.ctx, acc) - - // require a valid Ethereum tx to pass - to := ethcmn.BytesToAddress(addr2.Bytes()) - amt := big.NewInt(32) - gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) - - tx := newTestEthTx(input.ctx, ethMsg, priv1) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInsufficientFunds) -} - -func TestEthInvalidIntrinsicGas(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - addr1, priv1 := newTestAddrKey() - addr2, _ := newTestAddrKey() - - acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - // nolint:errcheck - acc.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc) - - // require a valid Ethereum tx to pass - to := ethcmn.BytesToAddress(addr2.Bytes()) - amt := big.NewInt(32) - gas := big.NewInt(20) - gasLimit := uint64(1000) - ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, gasLimit, gas, []byte("test")) - - tx := newTestEthTx(input.ctx, ethMsg, priv1) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInternal) -} - -func TestEthInvalidMempoolFees(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - input.ctx = input.ctx.WithMinGasPrices(sdk.DecCoins{sdk.NewDecCoin(types.DenomDefault, sdk.NewInt(500000))}) - - addr1, priv1 := newTestAddrKey() - addr2, _ := newTestAddrKey() - - acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - // nolint:errcheck - acc.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc) - - // require a valid Ethereum tx to pass - to := ethcmn.BytesToAddress(addr2.Bytes()) - amt := big.NewInt(32) - gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) - - tx := newTestEthTx(input.ctx, ethMsg, priv1) - requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInsufficientFee) -} - -func TestEthInvalidChainID(t *testing.T) { - input := newTestSetup() - input.ctx = input.ctx.WithBlockHeight(1) - - addr1, priv1 := newTestAddrKey() - addr2, _ := newTestAddrKey() - - acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1) - // nolint:errcheck - acc.SetCoins(newTestCoins()) - input.accKeeper.SetAccount(input.ctx, acc) - - // require a valid Ethereum tx to pass - to := ethcmn.BytesToAddress(addr2.Bytes()) - amt := big.NewInt(32) - gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) - - tx := newTestEthTx(input.ctx, ethMsg, priv1) - ctx := input.ctx.WithChainID("bad-chain-id") - requireInvalidTx(t, input.anteHandler, ctx, tx, false, types.CodeInvalidChainID) -} diff --git a/app/ethermint.go b/app/ethermint.go index 76197a73..79705be2 100644 --- a/app/ethermint.go +++ b/app/ethermint.go @@ -1,15 +1,13 @@ package app import ( - "encoding/json" + "io" "os" - emintcrypto "github.com/cosmos/ethermint/crypto" - "github.com/cosmos/ethermint/x/evm" - bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" @@ -26,12 +24,14 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" + "github.com/cosmos/ethermint/app/ante" + emintcrypto "github.com/cosmos/ethermint/crypto" eminttypes "github.com/cosmos/ethermint/types" - evmtypes "github.com/cosmos/ethermint/x/evm/types" + "github.com/cosmos/ethermint/x/evm" abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" - tmlog "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" ) @@ -44,21 +44,23 @@ var ( // DefaultNodeHome sets the folder where the applcation data and configuration will be stored DefaultNodeHome = os.ExpandEnv("$HOME/.emintd") - // ModuleBasics is the module BasicManager is in charge of setting up basic, + // ModuleBasics defines the module BasicManager is in charge of setting up basic, // non-dependant module elements, such as codec registration // and genesis verification. ModuleBasics = module.NewBasicManager( - genutil.AppModuleBasic{}, auth.AppModuleBasic{}, + supply.AppModuleBasic{}, + genutil.AppModuleBasic{}, bank.AppModuleBasic{}, staking.AppModuleBasic{}, mint.AppModuleBasic{}, distr.AppModuleBasic{}, - gov.NewAppModuleBasic(paramsclient.ProposalHandler, distr.ProposalHandler), + gov.NewAppModuleBasic( + paramsclient.ProposalHandler, distr.ProposalHandler, + ), params.AppModuleBasic{}, crisis.AppModuleBasic{}, slashing.AppModuleBasic{}, - supply.AppModuleBasic{}, evm.AppModuleBasic{}, ) @@ -71,6 +73,11 @@ var ( staking.NotBondedPoolName: {supply.Burner, supply.Staking}, gov.ModuleName: {supply.Burner}, } + + // module accounts that are allowed to receive tokens + allowedReceivingModAcc = map[string]bool{ + distr.ModuleName: true, + } ) // MakeCodec generates the necessary codecs for Amino @@ -100,18 +107,21 @@ type EthermintApp struct { keys map[string]*sdk.KVStoreKey tkeys map[string]*sdk.TransientStoreKey + // subspaces + subspaces map[string]params.Subspace + // keepers - accountKeeper auth.AccountKeeper - bankKeeper bank.Keeper - supplyKeeper supply.Keeper - stakingKeeper staking.Keeper - slashingKeeper slashing.Keeper - mintKeeper mint.Keeper - distrKeeper distr.Keeper - govKeeper gov.Keeper - crisisKeeper crisis.Keeper - paramsKeeper params.Keeper - evmKeeper evm.Keeper + AccountKeeper auth.AccountKeeper + BankKeeper bank.Keeper + SupplyKeeper supply.Keeper + StakingKeeper staking.Keeper + SlashingKeeper slashing.Keeper + MintKeeper mint.Keeper + DistrKeeper distr.Keeper + GovKeeper gov.Keeper + CrisisKeeper crisis.Keeper + ParamsKeeper params.Keeper + EvmKeeper evm.Keeper // the module manager mm *module.Manager @@ -124,18 +134,25 @@ type EthermintApp struct { // in a sovereign zone and as an application running with a shared security model. // For now, it will support only running as a sovereign application. func NewEthermintApp( - logger tmlog.Logger, db dbm.DB, loadLatest bool, - invCheckPeriod uint, baseAppOptions ...func(*bam.BaseApp)) *EthermintApp { + logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, + invCheckPeriod uint, baseAppOptions ...func(*bam.BaseApp), +) *EthermintApp { + cdc := MakeCodec() - bApp := bam.NewBaseApp(appName, logger, db, evmtypes.TxDecoder(cdc), baseAppOptions...) + // use custom Ethermint transaction decoder + bApp := bam.NewBaseApp(appName, logger, db, evm.TxDecoder(cdc), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetAppVersion(version.Version) - keys := sdk.NewKVStoreKeys(bam.MainStoreKey, auth.StoreKey, staking.StoreKey, + keys := sdk.NewKVStoreKeys( + bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, - gov.StoreKey, params.StoreKey, evmtypes.EvmStoreKey, evmtypes.EvmCodeKey) - blockKey := sdk.NewKVStoreKey(evmtypes.EvmBlockKey) - tkeys := sdk.NewTransientStoreKeys(staking.TStoreKey, params.TStoreKey) + gov.StoreKey, params.StoreKey, evm.CodeKey, evm.StoreKey, + ) + blockKey := sdk.NewKVStoreKey(evm.BlockKey) + + tkeys := sdk.NewTransientStoreKeys(params.TStoreKey) app := &EthermintApp{ BaseApp: bApp, @@ -143,89 +160,118 @@ func NewEthermintApp( invCheckPeriod: invCheckPeriod, keys: keys, tkeys: tkeys, + subspaces: make(map[string]params.Subspace), } // init params keeper and subspaces - app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace) - authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace) - bankSubspace := app.paramsKeeper.Subspace(bank.DefaultParamspace) - stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace) - mintSubspace := app.paramsKeeper.Subspace(mint.DefaultParamspace) - distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) - slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) - govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable()) - crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) + app.ParamsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace) + app.subspaces[auth.ModuleName] = app.ParamsKeeper.Subspace(auth.DefaultParamspace) + app.subspaces[bank.ModuleName] = app.ParamsKeeper.Subspace(bank.DefaultParamspace) + app.subspaces[staking.ModuleName] = app.ParamsKeeper.Subspace(staking.DefaultParamspace) + app.subspaces[mint.ModuleName] = app.ParamsKeeper.Subspace(mint.DefaultParamspace) + app.subspaces[distr.ModuleName] = app.ParamsKeeper.Subspace(distr.DefaultParamspace) + app.subspaces[slashing.ModuleName] = app.ParamsKeeper.Subspace(slashing.DefaultParamspace) + app.subspaces[gov.ModuleName] = app.ParamsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable()) + app.subspaces[crisis.ModuleName] = app.ParamsKeeper.Subspace(crisis.DefaultParamspace) - // add keepers - app.accountKeeper = auth.NewAccountKeeper(app.cdc, keys[auth.StoreKey], authSubspace, eminttypes.ProtoBaseAccount) - app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, bankSubspace, bank.DefaultCodespace, app.ModuleAccountAddrs()) - app.supplyKeeper = supply.NewKeeper(app.cdc, keys[supply.StoreKey], app.accountKeeper, app.bankKeeper, maccPerms) - stakingKeeper := staking.NewKeeper(app.cdc, keys[staking.StoreKey], - app.supplyKeeper, stakingSubspace, staking.DefaultCodespace) - app.mintKeeper = mint.NewKeeper(app.cdc, keys[mint.StoreKey], mintSubspace, &stakingKeeper, app.supplyKeeper, auth.FeeCollectorName) - app.distrKeeper = distr.NewKeeper(app.cdc, keys[distr.StoreKey], distrSubspace, &stakingKeeper, - app.supplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName, app.ModuleAccountAddrs()) - app.slashingKeeper = slashing.NewKeeper(app.cdc, keys[slashing.StoreKey], &stakingKeeper, - slashingSubspace, slashing.DefaultCodespace) - app.crisisKeeper = crisis.NewKeeper(crisisSubspace, invCheckPeriod, app.supplyKeeper, auth.FeeCollectorName) - app.evmKeeper = evm.NewKeeper(app.accountKeeper, keys[evmtypes.EvmStoreKey], keys[evmtypes.EvmCodeKey], blockKey, cdc) + // use custom Ethermint account for contracts + app.AccountKeeper = auth.NewAccountKeeper( + app.cdc, keys[auth.StoreKey], app.subspaces[auth.ModuleName], eminttypes.ProtoBaseAccount, + ) + app.BankKeeper = bank.NewBaseKeeper( + app.AccountKeeper, app.subspaces[bank.ModuleName], bank.DefaultCodespace, app.ModuleAccountAddrs(), + ) + app.SupplyKeeper = supply.NewKeeper( + app.cdc, keys[supply.StoreKey], app.AccountKeeper, app.BankKeeper, maccPerms, + ) + stakingKeeper := staking.NewKeeper( + app.cdc, keys[staking.StoreKey], app.SupplyKeeper, app.subspaces[staking.ModuleName], + staking.DefaultCodespace, + ) + app.MintKeeper = mint.NewKeeper( + app.cdc, keys[mint.StoreKey], app.subspaces[mint.ModuleName], &stakingKeeper, + app.SupplyKeeper, auth.FeeCollectorName, + ) + app.DistrKeeper = distr.NewKeeper( + app.cdc, keys[distr.StoreKey], app.subspaces[distr.ModuleName], &stakingKeeper, + app.SupplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName, app.ModuleAccountAddrs(), + ) + app.SlashingKeeper = slashing.NewKeeper( + app.cdc, keys[slashing.StoreKey], &stakingKeeper, app.subspaces[slashing.ModuleName], + slashing.DefaultCodespace, + ) + app.CrisisKeeper = crisis.NewKeeper( + app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName, + ) + app.EvmKeeper = evm.NewKeeper( + app.cdc, blockKey, keys[evm.CodeKey], keys[evm.StoreKey], app.AccountKeeper, + ) // register the proposal types govRouter := gov.NewRouter() govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler). - AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)). - AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)) - app.govKeeper = gov.NewKeeper(app.cdc, keys[gov.StoreKey], govSubspace, - app.supplyKeeper, &stakingKeeper, gov.DefaultCodespace, govRouter) + AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). + AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)) + app.GovKeeper = gov.NewKeeper( + app.cdc, keys[gov.StoreKey], app.subspaces[gov.ModuleName], app.SupplyKeeper, + &stakingKeeper, gov.DefaultCodespace, govRouter, + ) // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks - app.stakingKeeper = *stakingKeeper.SetHooks( - staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()), + app.StakingKeeper = *stakingKeeper.SetHooks( + staking.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), ) + // NOTE: Any module instantiated in the module manager that is later modified + // must be passed by reference here. app.mm = module.NewManager( - genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), - auth.NewAppModule(app.accountKeeper), - bank.NewAppModule(app.bankKeeper, app.accountKeeper), - crisis.NewAppModule(&app.crisisKeeper), - supply.NewAppModule(app.supplyKeeper, app.accountKeeper), - distr.NewAppModule(app.distrKeeper, app.accountKeeper, app.supplyKeeper, app.stakingKeeper), - gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper), - mint.NewAppModule(app.mintKeeper), - slashing.NewAppModule(app.slashingKeeper, app.accountKeeper, app.stakingKeeper), - staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), - evm.NewAppModule(app.evmKeeper), + genutil.NewAppModule(app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx), + auth.NewAppModule(app.AccountKeeper), + bank.NewAppModule(app.BankKeeper, app.AccountKeeper), + crisis.NewAppModule(&app.CrisisKeeper), + supply.NewAppModule(app.SupplyKeeper, app.AccountKeeper), + gov.NewAppModule(app.GovKeeper, app.AccountKeeper, app.SupplyKeeper), + mint.NewAppModule(app.MintKeeper), + slashing.NewAppModule(app.SlashingKeeper, app.AccountKeeper, app.StakingKeeper), + distr.NewAppModule(app.DistrKeeper, app.AccountKeeper, app.SupplyKeeper, app.StakingKeeper), + staking.NewAppModule(app.StakingKeeper, app.AccountKeeper, app.SupplyKeeper), + evm.NewAppModule(app.EvmKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that // there is nothing left over in the validator fee pool, so as to keep the // CanWithdrawInvariant invariant. - app.mm.SetOrderBeginBlockers(evmtypes.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName) - - app.mm.SetOrderEndBlockers(evmtypes.ModuleName, crisis.ModuleName, gov.ModuleName, staking.ModuleName) + app.mm.SetOrderBeginBlockers( + evm.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName, + ) + app.mm.SetOrderEndBlockers( + evm.ModuleName, crisis.ModuleName, gov.ModuleName, staking.ModuleName, + ) // NOTE: The genutils module must occur after staking so that pools are // properly initialized with tokens from genesis accounts. app.mm.SetOrderInitGenesis( - distr.ModuleName, staking.ModuleName, - auth.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, - mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, evmtypes.ModuleName, + auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName, + slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, + crisis.ModuleName, genutil.ModuleName, evm.ModuleName, ) - app.mm.RegisterInvariants(&app.crisisKeeper) + app.mm.RegisterInvariants(&app.CrisisKeeper) app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) // initialize stores app.MountKVStores(keys) app.MountTransientStores(tkeys) + // Mount block hash mapping key as DB (no need for historical queries) + // TODO: why does this need to be always StoreTypeDB? app.MountStore(blockKey, sdk.StoreTypeDB) // initialize BaseApp app.SetInitChainer(app.InitChainer) app.SetBeginBlocker(app.BeginBlocker) - app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.supplyKeeper)) + app.SetAnteHandler(ante.NewAnteHandler(app.AccountKeeper, app.SupplyKeeper)) app.SetEndBlocker(app.EndBlocker) if loadLatest { @@ -234,12 +280,12 @@ func NewEthermintApp( cmn.Exit(err.Error()) } } + return app } -// GenesisState is the state of the blockchain is represented here as a map of raw json -// messages key'd by a identifier string. -type GenesisState map[string]json.RawMessage +// Name returns the name of the App +func (app *EthermintApp) Name() string { return app.BaseApp.Name() } // BeginBlocker updates every begin block func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { @@ -253,7 +299,7 @@ func (app *EthermintApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) a // InitChainer updates at chain initialization func (app *EthermintApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - var genesisState GenesisState + var genesisState simapp.GenesisState app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState) return app.mm.InitGenesis(ctx, genesisState) } @@ -267,8 +313,42 @@ func (app *EthermintApp) LoadHeight(height int64) error { func (app *EthermintApp) ModuleAccountAddrs() map[string]bool { modAccAddrs := make(map[string]bool) for acc := range maccPerms { - modAccAddrs[app.supplyKeeper.GetModuleAddress(acc).String()] = true + modAccAddrs[supply.NewModuleAddress(acc).String()] = true } return modAccAddrs } + +// BlacklistedAccAddrs returns all the app's module account addresses black listed for receiving tokens. +func (app *EthermintApp) BlacklistedAccAddrs() map[string]bool { + blacklistedAddrs := make(map[string]bool) + for acc := range maccPerms { + blacklistedAddrs[supply.NewModuleAddress(acc).String()] = !allowedReceivingModAcc[acc] + } + + return blacklistedAddrs +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *EthermintApp) GetKey(storeKey string) *sdk.KVStoreKey { + return app.keys[storeKey] +} + +// Codec returns Ethermint's codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *EthermintApp) Codec() *codec.Codec { + return app.cdc +} + +// GetMaccPerms returns a copy of the module account permissions +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + for k, v := range maccPerms { + dupMaccPerms[k] = v + } + return dupMaccPerms +} diff --git a/app/ethermint_test.go b/app/ethermint_test.go index 4ba734d3..d9b75081 100644 --- a/app/ethermint_test.go +++ b/app/ethermint_test.go @@ -15,7 +15,7 @@ import ( func TestEthermintAppExport(t *testing.T) { db := dbm.NewMemDB() - app := NewEthermintApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, true, 0) + app := NewEthermintApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) genesisState := ModuleBasics.DefaultGenesis() stateBytes, err := codec.MarshalJSONIndent(app.cdc, genesisState) @@ -31,7 +31,7 @@ func TestEthermintAppExport(t *testing.T) { app.Commit() // Making a new app object with the db, so that initchain hasn't been called - app2 := NewEthermintApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, true, 0) + app2 := NewEthermintApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) _, _, err = app2.ExportAppStateAndValidators(false, []string{}) require.NoError(t, err, "ExportAppStateAndValidators should not have an error") } diff --git a/app/export.go b/app/export.go index 1784b0cf..4d4b29c3 100644 --- a/app/export.go +++ b/app/export.go @@ -35,7 +35,7 @@ func (app *EthermintApp) ExportAppStateAndValidators( } // Write validators to staking module to be used by TM node - validators = staking.WriteValidators(ctx, app.stakingKeeper) + validators = staking.WriteValidators(ctx, app.StakingKeeper) return appState, validators, nil } @@ -61,49 +61,48 @@ func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList } /* Just to be safe, assert the invariants on current state. */ - app.crisisKeeper.AssertInvariants(ctx) + app.CrisisKeeper.AssertInvariants(ctx) /* Handle fee distribution state. */ // withdraw all validator commission - app.stakingKeeper.IterateValidators(ctx, func(_ int64, val exported.ValidatorI) (stop bool) { - _, _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) + app.StakingKeeper.IterateValidators(ctx, func(_ int64, val exported.ValidatorI) (stop bool) { + _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) return false }) // withdraw all delegator rewards - dels := app.stakingKeeper.GetAllDelegations(ctx) + dels := app.StakingKeeper.GetAllDelegations(ctx) for _, delegation := range dels { - _, _ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) + _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) } // clear validator slash events - app.distrKeeper.DeleteAllValidatorSlashEvents(ctx) + app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) // clear validator historical rewards - app.distrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) // set context height to zero height := ctx.BlockHeight() ctx = ctx.WithBlockHeight(0) // reinitialize all validators - app.stakingKeeper.IterateValidators(ctx, func(_ int64, val exported.ValidatorI) (stop bool) { - + app.StakingKeeper.IterateValidators(ctx, func(_ int64, val exported.ValidatorI) (stop bool) { // donate any unwithdrawn outstanding reward fraction tokens to the community pool - scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) - feePool := app.distrKeeper.GetFeePool(ctx) + scraps := app.DistrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) + feePool := app.DistrKeeper.GetFeePool(ctx) feePool.CommunityPool = feePool.CommunityPool.Add(scraps) - app.distrKeeper.SetFeePool(ctx, feePool) + app.DistrKeeper.SetFeePool(ctx, feePool) - app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) + app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) return false }) // reinitialize all delegations for _, del := range dels { - app.distrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddress, del.ValidatorAddress) - app.distrKeeper.Hooks().AfterDelegationModified(ctx, del.DelegatorAddress, del.ValidatorAddress) + app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddress, del.ValidatorAddress) + app.DistrKeeper.Hooks().AfterDelegationModified(ctx, del.DelegatorAddress, del.ValidatorAddress) } // reset context height @@ -112,20 +111,20 @@ func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList /* Handle staking state. */ // iterate through redelegations, reset creation height - app.stakingKeeper.IterateRedelegations(ctx, func(_ int64, red staking.Redelegation) (stop bool) { + app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red staking.Redelegation) (stop bool) { for i := range red.Entries { red.Entries[i].CreationHeight = 0 } - app.stakingKeeper.SetRedelegation(ctx, red) + app.StakingKeeper.SetRedelegation(ctx, red) return false }) // iterate through unbonding delegations, reset creation height - app.stakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) (stop bool) { + app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) (stop bool) { for i := range ubd.Entries { ubd.Entries[i].CreationHeight = 0 } - app.stakingKeeper.SetUnbondingDelegation(ctx, ubd) + app.StakingKeeper.SetUnbondingDelegation(ctx, ubd) return false }) @@ -137,7 +136,7 @@ func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList for ; iter.Valid(); iter.Next() { addr := sdk.ValAddress(iter.Key()[1:]) - validator, found := app.stakingKeeper.GetValidator(ctx, addr) + validator, found := app.StakingKeeper.GetValidator(ctx, addr) if !found { panic("expected validator, not found") } @@ -147,22 +146,22 @@ func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList validator.Jailed = true } - app.stakingKeeper.SetValidator(ctx, validator) + app.StakingKeeper.SetValidator(ctx, validator) counter++ } iter.Close() - _ = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + _ = app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) /* Handle slashing state. */ // reset start height on signing infos - app.slashingKeeper.IterateValidatorSigningInfos( + app.SlashingKeeper.IterateValidatorSigningInfos( ctx, func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { info.StartHeight = 0 - app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info) return false }, ) diff --git a/app/test_helpers.go b/app/test_helpers.go new file mode 100644 index 00000000..f414121b --- /dev/null +++ b/app/test_helpers.go @@ -0,0 +1,34 @@ +package app + +import ( + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" +) + +// Setup initializes a new EthermintApp. A Nop logger is set in EthermintApp. +func Setup(isCheckTx bool) *EthermintApp { + db := dbm.NewMemDB() + app := NewEthermintApp(log.NewNopLogger(), db, nil, true, 0) + if !isCheckTx { + // init chain must be called to stop deliverState from being nil + genesisState := simapp.NewDefaultGenesisState() + stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState) + if err != nil { + panic(err) + } + + // Initialize the chain + app.InitChain( + abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + }, + ) + } + + return app +} diff --git a/app/test_utils.go b/app/test_utils.go deleted file mode 100644 index f114cf29..00000000 --- a/app/test_utils.go +++ /dev/null @@ -1,138 +0,0 @@ -package app - -import ( - "fmt" - "math/big" - "time" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/cosmos/cosmos-sdk/x/params" - - "github.com/cosmos/ethermint/crypto" - emint "github.com/cosmos/ethermint/types" - evmtypes "github.com/cosmos/ethermint/x/evm/types" - - ethcrypto "github.com/ethereum/go-ethereum/crypto" - abci "github.com/tendermint/tendermint/abci/types" - tmcrypto "github.com/tendermint/tendermint/crypto" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/log" - dbm "github.com/tendermint/tm-db" -) - -type testSetup struct { - ctx sdk.Context - cdc *codec.Codec - accKeeper auth.AccountKeeper - supplyKeeper types.SupplyKeeper - anteHandler sdk.AnteHandler -} - -func newTestSetup() testSetup { - db := dbm.NewMemDB() - authCapKey := sdk.NewKVStoreKey("authCapKey") - keySupply := sdk.NewKVStoreKey("keySupply") - keyParams := sdk.NewKVStoreKey("params") - tkeyParams := sdk.NewTransientStoreKey("transient_params") - - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeIAVL, db) - - if err := ms.LoadLatestVersion(); err != nil { - cmn.Exit(err.Error()) - } - - cdc := MakeCodec() - cdc.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) - - // Set params keeper and subspaces - paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) - authSubspace := paramsKeeper.Subspace(auth.DefaultParamspace) - - ctx := sdk.NewContext( - ms, - abci.Header{ChainID: "3", Time: time.Now().UTC()}, - true, - log.NewNopLogger(), - ) - - // Add keepers - accKeeper := auth.NewAccountKeeper(cdc, authCapKey, authSubspace, auth.ProtoBaseAccount) - accKeeper.SetParams(ctx, types.DefaultParams()) - supplyKeeper := mock.NewDummySupplyKeeper(accKeeper) - anteHandler := NewAnteHandler(accKeeper, supplyKeeper) - - return testSetup{ - ctx: ctx, - cdc: cdc, - accKeeper: accKeeper, - supplyKeeper: supplyKeeper, - anteHandler: anteHandler, - } -} - -func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { - return sdk.NewTestMsg(addrs...) -} - -func newTestCoins() sdk.Coins { - return sdk.Coins{sdk.NewInt64Coin(emint.DenomDefault, 500000000)} -} - -func newTestStdFee() auth.StdFee { - return auth.NewStdFee(220000, sdk.NewCoins(sdk.NewInt64Coin(emint.DenomDefault, 150))) -} - -// GenerateAddress generates an Ethereum address. -func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) { - privkey, _ := crypto.GenerateKey() - addr := ethcrypto.PubkeyToAddress(privkey.ToECDSA().PublicKey) - - return sdk.AccAddress(addr.Bytes()), privkey -} - -func newTestSDKTx( - ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey, - accNums []uint64, seqs []uint64, fee auth.StdFee, -) sdk.Tx { - - sigs := make([]auth.StdSignature, len(privs)) - for i, priv := range privs { - signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") - - sig, err := priv.Sign(signBytes) - if err != nil { - panic(err) - } - - sigs[i] = auth.StdSignature{ - PubKey: priv.PubKey(), - Signature: sig, - } - } - - return auth.NewStdTx(msgs, fee, sigs, "") -} - -func newTestEthTx(ctx sdk.Context, msg *evmtypes.EthereumTxMsg, priv tmcrypto.PrivKey) sdk.Tx { - chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) - if !ok { - panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID())) - } - - privkey, ok := priv.(crypto.PrivKeySecp256k1) - if !ok { - panic(fmt.Sprintf("invalid private key type: %T", priv)) - } - - msg.Sign(chainID, privkey.ToECDSA()) - return msg -} diff --git a/cmd/emintcli/export.go b/cmd/emintcli/export.go index 83c32451..56a05590 100644 --- a/cmd/emintcli/export.go +++ b/cmd/emintcli/export.go @@ -14,14 +14,15 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" clientkeys "github.com/cosmos/cosmos-sdk/client/keys" + emintcrypto "github.com/cosmos/ethermint/crypto" ) -func exportEthKeyCommand() *cobra.Command { +func unsafeExportEthKeyCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "export-eth-key ", - Short: "Export an Ethereum private key", - Long: `Export an Ethereum private key unencrypted to use in dev tooling **UNSAFE**`, + Use: "unsafe-export-eth-key [name]", + Short: "**UNSAFE** Export an Ethereum private key", + Long: `**UNSAFE** Export an Ethereum private key unencrypted to use in dev tooling`, Args: cobra.ExactArgs(1), RunE: runExportCmd, } @@ -29,12 +30,13 @@ func exportEthKeyCommand() *cobra.Command { } func runExportCmd(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) + kb, err := clientkeys.NewKeyringFromHomeFlag(cmd.InOrStdin()) if err != nil { return err } - buf := bufio.NewReader(cmd.InOrStdin()) decryptPassword := "" conf := true keyringBackend := viper.GetString(flags.FlagKeyringBackend) @@ -42,11 +44,11 @@ func runExportCmd(cmd *cobra.Command, args []string) error { case flags.KeyringBackendFile: decryptPassword, err = input.GetPassword( "**WARNING this is an unsafe way to export your unencrypted private key**\nEnter key password:", - buf) + inBuf) case flags.KeyringBackendOS: conf, err = input.GetConfirmation( "**WARNING** this is an unsafe way to export your unencrypted private key, are you sure?", - buf) + inBuf) } if err != nil || !conf { return err diff --git a/cmd/emintcli/keys.go b/cmd/emintcli/keys.go index 4238d240..4070c2a7 100644 --- a/cmd/emintcli/keys.go +++ b/cmd/emintcli/keys.go @@ -4,12 +4,13 @@ import ( "bufio" "io" + tmcrypto "github.com/tendermint/tendermint/crypto" + "github.com/cosmos/cosmos-sdk/client/flags" clientkeys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/crypto/keys" emintCrypto "github.com/cosmos/ethermint/crypto" - tmcrypto "github.com/tendermint/tendermint/crypto" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -46,7 +47,7 @@ func keyCommands() *cobra.Command { clientkeys.ParseKeyStringCommand(), clientkeys.MigrateCommand(), flags.LineBreak, - exportEthKeyCommand(), + unsafeExportEthKeyCommand(), ) return cmd } diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index 576e077a..37b92167 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -1,34 +1,38 @@ package main import ( + "fmt" "os" "path" - emintapp "github.com/cosmos/ethermint/app" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/ethermint/app" emintcrypto "github.com/cosmos/ethermint/crypto" "github.com/cosmos/ethermint/rpc" "github.com/tendermint/go-amino" + tmamino "github.com/tendermint/tendermint/crypto/encoding/amino" + "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client" clientkeys "github.com/cosmos/cosmos-sdk/client/keys" - sdkrpc "github.com/cosmos/cosmos-sdk/client/rpc" + clientrpc "github.com/cosmos/cosmos-sdk/client/rpc" cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/bank" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - tmamino "github.com/tendermint/tendermint/crypto/encoding/amino" - "github.com/tendermint/tendermint/libs/cli" ) func main() { + // Configure cobra to sort commands cobra.EnableCommandSorting = false - cdc := emintapp.MakeCodec() + cdc := app.MakeCodec() tmamino.RegisterKeyType(emintcrypto.PubKeySecp256k1{}, emintcrypto.PubKeyAminoName) tmamino.RegisterKeyType(emintcrypto.PrivKeySecp256k1{}, emintcrypto.PrivKeyAminoName) @@ -45,7 +49,7 @@ func main() { rootCmd := &cobra.Command{ Use: "emintcli", - Short: "Ethermint Client", + Short: "Command line interface for interacting with emintd", } // Add --chain-id to persistent flags and mark it required @@ -56,20 +60,24 @@ func main() { // Construct Root Command rootCmd.AddCommand( - sdkrpc.StatusCommand(), - client.ConfigCmd(emintapp.DefaultCLIHome), + clientrpc.StatusCommand(), + client.ConfigCmd(app.DefaultCLIHome), queryCmd(cdc), txCmd(cdc), rpc.EmintServeCmd(cdc), client.LineBreak, keyCommands(), client.LineBreak, + version.Cmd, + client.NewCompletionCmd(rootCmd, true), ) - executor := cli.PrepareMainCmd(rootCmd, "EM", emintapp.DefaultCLIHome) + // Add flags and prefix all env exposed with EM + executor := cli.PrepareMainCmd(rootCmd, "EM", app.DefaultCLIHome) + err := executor.Execute() if err != nil { - panic(err) + panic(fmt.Errorf("failed executing CLI command: %w", err)) } } @@ -89,7 +97,7 @@ func queryCmd(cdc *amino.Codec) *cobra.Command { ) // add modules' query commands - emintapp.ModuleBasics.AddQueryCommands(queryCmd, cdc) + app.ModuleBasics.AddQueryCommands(queryCmd, cdc) return queryCmd } @@ -104,14 +112,27 @@ func txCmd(cdc *amino.Codec) *cobra.Command { bankcmd.SendTxCmd(cdc), client.LineBreak, authcmd.GetSignCommand(cdc), + authcmd.GetMultiSignCommand(cdc), client.LineBreak, authcmd.GetBroadcastCommand(cdc), authcmd.GetEncodeCommand(cdc), + authcmd.GetDecodeCommand(cdc), client.LineBreak, ) // add modules' tx commands - emintapp.ModuleBasics.AddTxCommands(txCmd, cdc) + app.ModuleBasics.AddTxCommands(txCmd, cdc) + + // remove auth and bank commands as they're mounted under the root tx command + var cmdsToRemove []*cobra.Command + + for _, cmd := range txCmd.Commands() { + if cmd.Use == auth.ModuleName || cmd.Use == bank.ModuleName { + cmdsToRemove = append(cmdsToRemove, cmd) + } + } + + txCmd.RemoveCommand(cmdsToRemove...) return txCmd } diff --git a/client/genaccounts/main.go b/cmd/emintd/genaccounts.go similarity index 96% rename from client/genaccounts/main.go rename to cmd/emintd/genaccounts.go index 46c68e8e..fe6c02f5 100644 --- a/client/genaccounts/main.go +++ b/cmd/emintd/genaccounts.go @@ -1,4 +1,4 @@ -package genaccounts +package main import ( "bufio" @@ -19,6 +19,8 @@ import ( authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting" "github.com/cosmos/cosmos-sdk/x/genutil" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + ethermint "github.com/cosmos/ethermint/types" ) @@ -96,7 +98,10 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa return errors.New("invalid vesting parameters; must supply start and end time or end time") } } else { - genAccount = ethermint.Account{BaseAccount: baseAccount} + genAccount = ethermint.Account{ + BaseAccount: baseAccount, + CodeHash: ethcrypto.Keccak256(nil), + } } if err := genAccount.Validate(); err != nil { diff --git a/cmd/emintd/main.go b/cmd/emintd/main.go index 38930a6d..34bdb1e7 100644 --- a/cmd/emintd/main.go +++ b/cmd/emintd/main.go @@ -23,17 +23,20 @@ import ( "github.com/spf13/viper" "github.com/cosmos/ethermint/app" - "github.com/cosmos/ethermint/client/genaccounts" emintcrypto "github.com/cosmos/ethermint/crypto" abci "github.com/tendermint/tendermint/abci/types" tmamino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/libs/cli" - tmlog "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" ) +const flagInvCheckPeriod = "inv-check-period" + +var invCheckPeriod uint + func main() { cobra.EnableCommandSorting = false @@ -65,12 +68,14 @@ func main() { withChainIDValidation(genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome)), genutilcli.CollectGenTxsCmd(ctx, cdc, auth.GenesisAccountIterator{}, app.DefaultNodeHome), genutilcli.GenTxCmd( - ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{}, auth.GenesisAccountIterator{}, app.DefaultNodeHome, app.DefaultCLIHome, + ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{}, auth.GenesisAccountIterator{}, + app.DefaultNodeHome, app.DefaultCLIHome, ), genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics), // AddGenesisAccountCmd allows users to add accounts to the genesis file - genaccounts.AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome), + AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome), + client.NewCompletionCmd(rootCmd, true), ) // Tendermint node base commands @@ -78,23 +83,25 @@ func main() { // prepare and add flags executor := cli.PrepareBaseCmd(rootCmd, "EM", app.DefaultNodeHome) + rootCmd.PersistentFlags().UintVar(&invCheckPeriod, flagInvCheckPeriod, + 0, "Assert registered invariants every N blocks") err := executor.Execute() if err != nil { panic(err) } } -func newApp(logger tmlog.Logger, db dbm.DB, traceStore io.Writer) abci.Application { - return app.NewEthermintApp(logger, db, true, 0, +func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { + return app.NewEthermintApp(logger, db, traceStore, true, 0, baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning")))) } func exportAppStateAndTMValidators( - logger tmlog.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string, + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string, ) (json.RawMessage, []tmtypes.GenesisValidator, error) { if height != -1 { - emintApp := app.NewEthermintApp(logger, db, true, 0) + emintApp := app.NewEthermintApp(logger, db, traceStore, true, 0) err := emintApp.LoadHeight(height) if err != nil { return nil, nil, err @@ -102,7 +109,7 @@ func exportAppStateAndTMValidators( return emintApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) } - emintApp := app.NewEthermintApp(logger, db, true, 0) + emintApp := app.NewEthermintApp(logger, db, traceStore, true, 0) return emintApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) } @@ -119,8 +126,7 @@ func withChainIDValidation(baseCmd *cobra.Command) *cobra.Command { // Verify that the chain-id entered is a base 10 integer _, ok := new(big.Int).SetString(chainIDFlag, 10) if !ok { - return fmt.Errorf( - fmt.Sprintf("invalid chainID: %s, must be base-10 integer format", chainIDFlag)) + return fmt.Errorf("invalid chainID: %s, must be base-10 integer format", chainIDFlag) } return baseRunE(cmd, args) diff --git a/go.mod b/go.mod index e3ad3ac4..22603c77 100644 --- a/go.mod +++ b/go.mod @@ -53,3 +53,5 @@ require ( gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/yaml.v2 v2.2.8 ) + +replace github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.34.4-0.20191213112149-d7b0f4b9b4fb diff --git a/go.sum b/go.sum index ab739a0b..a12bf190 100644 --- a/go.sum +++ b/go.sum @@ -314,8 +314,6 @@ github.com/spf13/cobra v0.0.1 h1:zZh3X5aZbdnoj+4XkaBxKfhO4ot82icYdhhREIAXIj8= github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= diff --git a/importer/importer_test.go b/importer/importer_test.go index 847c66f0..13371df5 100644 --- a/importer/importer_test.go +++ b/importer/importer_test.go @@ -44,14 +44,11 @@ var ( flagBlockchain string flagCPUProfile string - // miner501 = ethcmn.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") genInvestor = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0") - // paramsKey = sdk.NewKVStoreKey("params") - // tParamsKey = sdk.NewTransientStoreKey("transient_params") accKey = sdk.NewKVStoreKey("acc") - storageKey = sdk.NewKVStoreKey(evmtypes.EvmStoreKey) - codeKey = sdk.NewKVStoreKey(evmtypes.EvmCodeKey) + storageKey = sdk.NewKVStoreKey(evmtypes.StoreKey) + codeKey = sdk.NewKVStoreKey(evmtypes.CodeKey) logger = tmlog.NewNopLogger() @@ -104,7 +101,7 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak auth.Accoun ms := cms.CacheMultiStore() ctx := sdk.NewContext(ms, abci.Header{}, false, logger) - stateDB := evmtypes.NewCommitStateDB(ctx, ak, storageKey, codeKey) + stateDB := evmtypes.NewCommitStateDB(ctx, codeKey, storageKey, ak) // sort the addresses and insertion of key/value pairs matters genAddrs := make([]string, len(genBlock.Alloc)) @@ -270,7 +267,7 @@ func TestImportBlocks(t *testing.T) { } func createStateDB(ctx sdk.Context, ak auth.AccountKeeper) *evmtypes.CommitStateDB { - stateDB := evmtypes.NewCommitStateDB(ctx, ak, storageKey, codeKey) + stateDB := evmtypes.NewCommitStateDB(ctx, codeKey, storageKey, ak) return stateDB } @@ -346,12 +343,18 @@ func applyTransaction(config *ethparams.ChainConfig, bc ethcore.ChainContext, au return nil, 0, err } // Update the state with pending changes - var root []byte + var intRoot ethcmn.Hash if config.IsByzantium(header.Number) { - statedb.Finalise(true) + err = statedb.Finalise(true) } else { - root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() + intRoot, err = statedb.IntermediateRoot(config.IsEIP158(header.Number)) } + + if err != nil { + return nil, gas, err + } + + root := intRoot.Bytes() *usedGas += gas // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx @@ -364,7 +367,7 @@ func applyTransaction(config *ethparams.ChainConfig, bc ethcore.ChainContext, au receipt.ContractAddress = ethcrypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) } // Set the receipt logs and create a bloom for filtering - receipt.Logs = statedb.GetLogs(tx.Hash()) + receipt.Logs, err = statedb.GetLogs(tx.Hash()) receipt.Bloom = ethtypes.CreateBloom(ethtypes.Receipts{receipt}) receipt.BlockHash = statedb.BlockHash() receipt.BlockNumber = header.Number diff --git a/rpc/eth_api.go b/rpc/eth_api.go index 502c9a67..fc460d39 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -2,13 +2,15 @@ package rpc import ( "bytes" + "errors" "fmt" "log" "math/big" "strconv" "sync" - "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/spf13/viper" + emintcrypto "github.com/cosmos/ethermint/crypto" params "github.com/cosmos/ethermint/rpc/args" emint "github.com/cosmos/ethermint/types" @@ -29,10 +31,10 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" authutils "github.com/cosmos/cosmos-sdk/x/auth/client/utils" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/spf13/viper" ) // PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. @@ -210,7 +212,7 @@ func (e *PublicEthAPI) getBlockTransactionCountByNumber(number int64) *hexutil.U return nil } - n := hexutil.Uint(block.Block.NumTxs) + n := hexutil.Uint(len(block.Block.Txs)) return &n } @@ -277,8 +279,7 @@ func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, err // parse the chainID from a string to a base-10 integer intChainID, ok := new(big.Int).SetString(chainID, 10) if !ok { - return common.Hash{}, fmt.Errorf( - fmt.Sprintf("invalid chainID: %s, must be integer format", chainID)) + return common.Hash{}, fmt.Errorf("invalid chainID: %s, must be integer format", chainID) } // Sign transaction @@ -306,7 +307,7 @@ func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, err // SendRawTransaction send a raw Ethereum transaction. func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { - tx := new(types.EthereumTxMsg) + tx := new(types.MsgEthereumTx) // RLP decode raw transaction bytes if err := rlp.DecodeBytes(data, tx); err != nil { @@ -351,9 +352,12 @@ func (e *PublicEthAPI) Call(args CallArgs, blockNr rpc.BlockNumber, overrides *m return []byte{}, err } - _, _, ret, err := types.DecodeReturnData(result.Data) + data, err := types.DecodeResultData(result.Data) + if err != nil { + return []byte{}, err + } - return (hexutil.Bytes)(ret), err + return (hexutil.Bytes)(data.Ret), nil } // account indicates the overriding fields of account during the execution of @@ -370,8 +374,9 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -// DoCall performs a simulated call operation through the evm -func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasCap *big.Int) (sdk.Result, error) { +// DoCall performs a simulated call operation through the evm. It returns the +// estimated gas used on the operation or an error if fails. +func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasCap *big.Int) (*sdk.Result, error) { // Set height for historical queries ctx := e.cliCtx if blockNr.Int64() != 0 { @@ -425,7 +430,7 @@ func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasC } // Create new call message - msg := types.NewEmintMsg(0, &toAddr, sdk.NewIntFromBigInt(value), gas, + msg := types.NewMsgEthermint(0, &toAddr, sdk.NewIntFromBigInt(value), gas, sdk.NewIntFromBigInt(gasPrice), data, sdk.AccAddress(addr.Bytes())) // Generate tx to be used to simulate (signature isn't needed) @@ -436,24 +441,26 @@ func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasC txEncoder := authutils.GetTxEncoder(ctx.Codec) txBytes, err := txEncoder(tx) if err != nil { - return sdk.Result{}, err + return nil, err } // Transaction simulation through query res, _, err := ctx.QueryWithData("app/simulate", txBytes) if err != nil { - return sdk.Result{}, err + return nil, err } var simResult sdk.Result if err = e.cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res, &simResult); err != nil { - return sdk.Result{}, err + return nil, err } - return simResult, nil + return &simResult, nil } -// EstimateGas estimates gas usage for the given smart contract call. +// EstimateGas returns an estimate of gas usage for the given smart contract call. +// It adds 1,000 gas to the returned value instead of using the gas adjustment +// param from the SDK. func (e *PublicEthAPI) EstimateGas(args CallArgs) (hexutil.Uint64, error) { result, err := e.doCall(args, 0, big.NewInt(emint.DefaultRPCGasLimit)) if err != nil { @@ -482,31 +489,37 @@ func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[ return e.getEthBlockByNumber(value, fullTx) } -func (e *PublicEthAPI) getEthBlockByNumber(value int64, fullTx bool) (map[string]interface{}, error) { +func (e *PublicEthAPI) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) { // Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014) var blkNumPtr *int64 - if value != 0 { - blkNumPtr = &value + if height != 0 { + blkNumPtr = &height } block, err := e.cliCtx.Client.Block(blkNumPtr) if err != nil { return nil, err } - header := block.BlockMeta.Header + header := block.Block.Header gasLimit, err := e.getGasLimit() if err != nil { return nil, err } - var gasUsed *big.Int - var transactions []interface{} + var ( + gasUsed *big.Int + transactions []interface{} + ) if fullTx { // Populate full transaction data - transactions, gasUsed = convertTransactionsToRPC(e.cliCtx, block.Block.Txs, - common.BytesToHash(header.Hash()), uint64(header.Height)) + transactions, gasUsed, err = convertTransactionsToRPC( + e.cliCtx, block.Block.Txs, common.BytesToHash(header.Hash()), uint64(header.Height), + ) + if err != nil { + return nil, err + } } else { // TODO: Gas used not saved and cannot be calculated by hashes // Return slice of transaction hashes @@ -553,19 +566,24 @@ func formatBlock( } } -func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]interface{}, *big.Int) { +func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]interface{}, *big.Int, error) { transactions := make([]interface{}, len(txs)) gasUsed := big.NewInt(0) + for i, tx := range txs { ethTx, err := bytesToEthTx(cliCtx, tx) if err != nil { - continue + return nil, nil, err } // TODO: Remove gas usage calculation if saving gasUsed per block gasUsed.Add(gasUsed, ethTx.Fee()) - transactions[i] = newRPCTransaction(ethTx, blockHash, &height, uint64(i)) + transactions[i], err = newRPCTransaction(*ethTx, blockHash, &height, uint64(i)) + if err != nil { + return nil, nil, err + } } - return transactions, gasUsed + + return transactions, gasUsed, nil } // Transaction represents a transaction returned to RPC clients. @@ -586,23 +604,30 @@ type Transaction struct { S *hexutil.Big `json:"s"` } -func bytesToEthTx(cliCtx context.CLIContext, bz []byte) (*types.EthereumTxMsg, error) { +func bytesToEthTx(cliCtx context.CLIContext, bz []byte) (*types.MsgEthereumTx, error) { var stdTx sdk.Tx err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(bz, &stdTx) - ethTx, ok := stdTx.(*types.EthereumTxMsg) - if !ok || err != nil { + if err != nil { + return nil, err + } + + ethTx, ok := stdTx.(types.MsgEthereumTx) + if !ok { return nil, fmt.Errorf("invalid transaction type, must be an amino encoded Ethereum transaction") } - return ethTx, nil + return ðTx, nil } // newRPCTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). -func newRPCTransaction(tx *types.EthereumTxMsg, blockHash common.Hash, blockNumber *uint64, index uint64) *Transaction { +func newRPCTransaction(tx types.MsgEthereumTx, blockHash common.Hash, blockNumber *uint64, index uint64) (*Transaction, error) { // Verify signature and retrieve sender address - from, _ := tx.VerifySig(tx.ChainID()) + from, err := tx.VerifySig(tx.ChainID()) + if err != nil { + return nil, err + } - result := &Transaction{ + result := Transaction{ From: from, Gas: hexutil.Uint64(tx.Data.GasLimit), GasPrice: (*hexutil.Big)(tx.Data.Price), @@ -615,12 +640,14 @@ func newRPCTransaction(tx *types.EthereumTxMsg, blockHash common.Hash, blockNumb R: (*hexutil.Big)(tx.Data.R), S: (*hexutil.Big)(tx.Data.S), } + if blockHash != (common.Hash{}) { result.BlockHash = &blockHash result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(*blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } - return result + + return &result, nil } // GetTransactionByHash returns the transaction identified by hash. @@ -636,7 +663,7 @@ func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) (*Transaction, err if err != nil { return nil, err } - blockHash := common.BytesToHash(block.BlockMeta.Header.Hash()) + blockHash := common.BytesToHash(block.Block.Header.Hash()) ethTx, err := bytesToEthTx(e.cliCtx, tx.Tx) if err != nil { @@ -644,7 +671,7 @@ func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) (*Transaction, err } height := uint64(tx.Height) - return newRPCTransaction(ethTx, blockHash, &height, uint64(tx.Index)), nil + return newRPCTransaction(*ethTx, blockHash, &height, uint64(tx.Index)) } // GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. @@ -670,7 +697,7 @@ func (e *PublicEthAPI) getTransactionByBlockNumberAndIndex(number int64, idx hex if err != nil { return nil, err } - header := block.BlockMeta.Header + header := block.Block.Header txs := block.Block.Txs if uint64(idx) >= uint64(len(txs)) { @@ -682,8 +709,7 @@ func (e *PublicEthAPI) getTransactionByBlockNumberAndIndex(number int64, idx hex } height := uint64(header.Height) - transaction := newRPCTransaction(ethTx, common.BytesToHash(header.Hash()), &height, uint64(idx)) - return transaction, nil + return newRPCTransaction(*ethTx, common.BytesToHash(header.Hash()), &height, uint64(idx)) } // GetTransactionReceipt returns the transaction receipt identified by hash. @@ -699,7 +725,7 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter if err != nil { return nil, err } - blockHash := common.BytesToHash(block.BlockMeta.Header.Hash()) + blockHash := common.BytesToHash(block.Block.Header.Hash()) // Convert tx bytes to eth transaction ethTx, err := bytesToEthTx(e.cliCtx, tx.Tx) @@ -726,7 +752,10 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter e.cliCtx.Codec.MustUnmarshalJSON(res, &logs) txData := tx.TxResult.GetData() - contractAddress, bloomFilter, _, _ := types.DecodeReturnData(txData) + data, err := types.DecodeResultData(txData) + if err != nil { + return nil, err + } fields := map[string]interface{}{ "blockHash": blockHash, @@ -739,12 +768,12 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter "cumulativeGasUsed": nil, // ignore until needed "contractAddress": nil, "logs": logs.Logs, - "logsBloom": bloomFilter, + "logsBloom": data.Bloom, "status": status, } - if contractAddress != (common.Address{}) { - fields["contractAddress"] = contractAddress + if data.Address != (common.Address{}) { + fields["contractAddress"] = data.Address } return fields, nil @@ -766,7 +795,11 @@ func (e *PublicEthAPI) PendingTransactions() ([]*Transaction, error) { } // * Should check signer and reference against accounts the node manages in future - rpcTx := newRPCTransaction(ethTx, common.Hash{}, nil, 0) + rpcTx, err := newRPCTransaction(*ethTx, common.Hash{}, nil, 0) + if err != nil { + return nil, err + } + transactions = append(transactions, rpcTx) } @@ -871,13 +904,14 @@ func (e *PublicEthAPI) getGasLimit() (int64, error) { } // generateFromArgs populates tx message with args (used in RPC API) -func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (msg *types.EthereumTxMsg, err error) { - var nonce uint64 - - var gasLimit uint64 +func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (*types.MsgEthereumTx, error) { + var ( + nonce uint64 + gasLimit uint64 + err error + ) amount := (*big.Int)(args.Value) - gasPrice := (*big.Int)(args.GasPrice) if args.GasPrice == nil { @@ -899,7 +933,7 @@ func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (msg *types.Ethe } if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { - return nil, fmt.Errorf(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) + return nil, errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) } // Sets input to either Input or Data, if both are set and not equal error above returns @@ -916,6 +950,7 @@ func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (msg *types.Ethe return nil, fmt.Errorf("contract creation without any data provided") } } + if args.Gas == nil { callArgs := CallArgs{ From: &args.From, @@ -925,11 +960,15 @@ func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (msg *types.Ethe Value: args.Value, Data: args.Data, } - g, _ := e.EstimateGas(callArgs) + g, err := e.EstimateGas(callArgs) + if err != nil { + return nil, err + } gasLimit = uint64(g) } else { gasLimit = (uint64)(*args.Gas) } + msg := types.NewMsgEthereumTx(nonce, args.To, amount, gasLimit, gasPrice, input) - return types.NewEthereumTxMsg(nonce, args.To, amount, gasLimit, gasPrice, input), nil + return &msg, nil } diff --git a/rpc/tester/tester_test.go b/rpc/tester/tester_test.go index cc9342a1..4317304b 100644 --- a/rpc/tester/tester_test.go +++ b/rpc/tester/tester_test.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/ethermint/version" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" ) const ( @@ -92,51 +93,34 @@ func TestEth_protocolVersion(t *testing.T) { expectedRes := hexutil.Uint(version.ProtocolVersion) rpcRes, err := call("eth_protocolVersion", []string{}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var res hexutil.Uint err = res.UnmarshalJSON(rpcRes.Result) - - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Logf("Got protocol version: %s\n", res.String()) - - if res != expectedRes { - t.Fatalf("expected: %s got: %s\n", expectedRes.String(), rpcRes.Result) - } + require.Equal(t, expectedRes, res, "expected: %s got: %s\n", expectedRes.String(), rpcRes.Result) } func TestEth_blockNumber(t *testing.T) { rpcRes, err := call("eth_blockNumber", []string{}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + var res hexutil.Uint64 err = res.UnmarshalJSON(rpcRes.Result) - - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Logf("Got block number: %s\n", res.String()) } func TestEth_GetBalance(t *testing.T) { rpcRes, err := call("eth_getBalance", []string{addrA, "0x0"}) - if err != nil { - t.Fatal(err) - return - } + require.NoError(t, err) var res hexutil.Big err = res.UnmarshalJSON(rpcRes.Result) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Logf("Got balance %s for %s\n", res.String(), addrA) @@ -149,40 +133,27 @@ func TestEth_GetBalance(t *testing.T) { func TestEth_GetStorageAt(t *testing.T) { expectedRes := hexutil.Bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} rpcRes, err := call("eth_getStorageAt", []string{addrA, string(addrAStoreKey), "0x0"}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var storage hexutil.Bytes err = storage.UnmarshalJSON(rpcRes.Result) - - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Logf("Got value [%X] for %s with key %X\n", storage, addrA, addrAStoreKey) - if !bytes.Equal(storage, expectedRes) { - t.Errorf("expected: %d (%d bytes) got: %d (%d bytes)", expectedRes, len(expectedRes), storage, len(storage)) - } + require.True(t, bytes.Equal(storage, expectedRes), "expected: %d (%d bytes) got: %d (%d bytes)", expectedRes, len(expectedRes), storage, len(storage)) } func TestEth_GetCode(t *testing.T) { expectedRes := hexutil.Bytes{} rpcRes, err := call("eth_getCode", []string{addrA, "0x0"}) - if err != nil { - t.Error(err) - } + require.NoError(t, err) var code hexutil.Bytes err = code.UnmarshalJSON(rpcRes.Result) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Logf("Got code [%X] for %s\n", code, addrA) - if !bytes.Equal(expectedRes, code) { - t.Errorf("expected: %X got: %X", expectedRes, code) - } + require.True(t, bytes.Equal(expectedRes, code), "expected: %X got: %X", expectedRes, code) } diff --git a/x/evm/abci.go b/x/evm/abci.go new file mode 100644 index 00000000..daf8d91b --- /dev/null +++ b/x/evm/abci.go @@ -0,0 +1,45 @@ +package evm + +import ( + "math/big" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +// BeginBlock sets the Bloom and Hash mappings and resets the Bloom filter and +// the transaction count to 0. +func BeginBlock(k Keeper, ctx sdk.Context, req abci.RequestBeginBlock) { + // Consider removing this when using evm as module without web3 API + bloom := ethtypes.BytesToBloom(k.Bloom.Bytes()) + err := k.SetBlockBloomMapping(ctx, bloom, req.Header.GetHeight()-1) + if err != nil { + panic(err) + } + k.SetBlockHashMapping(ctx, req.Header.LastBlockId.GetHash(), req.Header.GetHeight()-1) + k.Bloom = big.NewInt(0) + k.TxCount = 0 +} + +// EndBlock updates the accounts and commits states objects to the KV Store +func EndBlock(k Keeper, ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + // Gas costs are handled within msg handler so costs should be ignored + ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + + // Update account balances before committing other parts of state + k.CommitStateDB.UpdateAccounts() + + // Commit state objects to KV store + _, err := k.CommitStateDB.WithContext(ctx).Commit(true) + if err != nil { + panic(err) + } + + // Clear accounts cache after account data has been committed + k.CommitStateDB.ClearStateObjects() + + return []abci.ValidatorUpdate{} +} diff --git a/x/evm/alias.go b/x/evm/alias.go index a4269454..dc5e2822 100644 --- a/x/evm/alias.go +++ b/x/evm/alias.go @@ -5,7 +5,13 @@ import ( "github.com/cosmos/ethermint/x/evm/types" ) +// nolint const ( + ModuleName = types.ModuleName + StoreKey = types.StoreKey + CodeKey = types.StoreKey + BlockKey = types.BlockKey + RouterKey = types.RouterKey QueryProtocolVersion = types.QueryProtocolVersion QueryBalance = types.QueryBalance QueryBlockNumber = types.QueryBlockNumber @@ -19,10 +25,13 @@ const ( QueryAccount = types.QueryAccount ) +// nolint var ( NewKeeper = keeper.NewKeeper + TxDecoder = types.TxDecoder ) +//nolint type ( Keeper = keeper.Keeper QueryResAccount = types.QueryResAccount diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index d8ecd3f9..2f2ec78d 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -85,7 +85,7 @@ func GetCmdGenTx(cdc *codec.Codec) *cobra.Command { } // TODO: Potentially allow overriding of gas price and gas limit - msg := types.NewEmintMsg(seq, &toAddr, sdk.NewInt(amount), txBldr.Gas(), + msg := types.NewMsgEthermint(seq, &toAddr, sdk.NewInt(amount), txBldr.Gas(), sdk.NewInt(emint.DefaultGasPrice), data, from) err = msg.ValidateBasic() @@ -137,7 +137,7 @@ func GetCmdGenCreateTx(cdc *codec.Codec) *cobra.Command { } // TODO: Potentially allow overriding of gas price and gas limit - msg := types.NewEmintMsg(seq, nil, sdk.NewInt(amount), txBldr.Gas(), + msg := types.NewMsgEthermint(seq, nil, sdk.NewInt(amount), txBldr.Gas(), sdk.NewInt(emint.DefaultGasPrice), data, from) err = msg.ValidateBasic() diff --git a/x/evm/handler.go b/x/evm/handler.go index 07d8d193..4c08984d 100644 --- a/x/evm/handler.go +++ b/x/evm/handler.go @@ -18,23 +18,20 @@ import ( func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { switch msg := msg.(type) { - case types.EthereumTxMsg: - return handleETHTxMsg(ctx, k, msg) - case *types.EmintMsg: - return handleEmintMsg(ctx, k, *msg) + case types.MsgEthereumTx: + return HandleMsgEthereumTx(ctx, k, msg) + case types.MsgEthermint: + return HandleMsgEthermint(ctx, k, msg) default: - errMsg := fmt.Sprintf("Unrecognized ethermint Msg type: %v", msg.Type()) + errMsg := fmt.Sprintf("unrecognized ethermint msg type: %v", msg.Type()) return sdk.ErrUnknownRequest(errMsg).Result() } } } -// Handle an Ethereum specific tx -func handleETHTxMsg(ctx sdk.Context, k Keeper, msg types.EthereumTxMsg) sdk.Result { - if err := msg.ValidateBasic(); err != nil { - return err.Result() - } - +// HandleMsgEthereumTx handles an Ethereum specific tx +func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) // parse the chainID from a string to a base-10 integer intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) if !ok { @@ -44,14 +41,14 @@ func handleETHTxMsg(ctx sdk.Context, k Keeper, msg types.EthereumTxMsg) sdk.Resu // Verify signature and retrieve sender address sender, err := msg.VerifySig(intChainID) if err != nil { - return emint.ErrInvalidSender(err.Error()).Result() + return sdk.ResultFromError(err) } // Encode transaction by default Tx encoder txEncoder := authutils.GetTxEncoder(types.ModuleCdc) txBytes, err := txEncoder(msg) if err != nil { - return sdk.ErrInternal(err.Error()).Result() + return sdk.ResultFromError(err) } txHash := tmtypes.Tx(txBytes).Hash() ethHash := common.BytesToHash(txHash) @@ -70,21 +67,53 @@ func handleETHTxMsg(ctx sdk.Context, k Keeper, msg types.EthereumTxMsg) sdk.Resu Simulate: ctx.IsCheckTx(), } // Prepare db for logs - k.CommitStateDB.Prepare(ethHash, common.Hash{}, k.TxCount.Get()) - k.TxCount.Increment() + k.CommitStateDB.Prepare(ethHash, common.Hash{}, k.TxCount) + k.TxCount++ - bloom, res := st.TransitionCSDB(ctx) - if res.IsOK() { - k.Bloom.Or(k.Bloom, bloom) + // TODO: move to keeper + returnData, err := st.TransitionCSDB(ctx) + if err != nil { + return sdk.ResultFromError(err) } - return res + + // update block bloom filter + k.Bloom.Or(k.Bloom, returnData.Bloom) + + // update transaction logs in KVStore + err = k.SetTransactionLogs(ctx, returnData.Logs, txHash) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeEthereumTx, + sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Data.Amount.String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, sender.String()), + ), + }) + + if msg.Data.Recipient != nil { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeEthereumTx, + sdk.NewAttribute(types.AttributeKeyRecipient, msg.Data.Recipient.String()), + ), + ) + } + + // set the events to the result + returnData.Result.Events = ctx.EventManager().Events() + return *returnData.Result } -func handleEmintMsg(ctx sdk.Context, k Keeper, msg types.EmintMsg) sdk.Result { - if err := msg.ValidateBasic(); err != nil { - return err.Result() - } - +// HandleMsgEthermint handles a MsgEthermint +func HandleMsgEthermint(ctx sdk.Context, k Keeper, msg types.MsgEthermint) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) // parse the chainID from a string to a base-10 integer intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) if !ok { @@ -109,9 +138,36 @@ func handleEmintMsg(ctx sdk.Context, k Keeper, msg types.EmintMsg) sdk.Result { } // Prepare db for logs - k.CommitStateDB.Prepare(common.Hash{}, common.Hash{}, k.TxCount.Get()) // Cannot provide tx hash - k.TxCount.Increment() + k.CommitStateDB.Prepare(common.Hash{}, common.Hash{}, k.TxCount) // Cannot provide tx hash + k.TxCount++ - _, res := st.TransitionCSDB(ctx) - return res + returnData, err := st.TransitionCSDB(ctx) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeEthermint, + sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.From.String()), + ), + }) + + if msg.Recipient != nil { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeEthermint, + sdk.NewAttribute(types.AttributeKeyRecipient, msg.Recipient.String()), + ), + ) + } + + // set the events to the result + returnData.Result.Events = ctx.EventManager().Events() + return *returnData.Result } diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go new file mode 100644 index 00000000..4419bd77 --- /dev/null +++ b/x/evm/handler_test.go @@ -0,0 +1,89 @@ +package evm_test + +import ( + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/cosmos/ethermint/app" + "github.com/cosmos/ethermint/x/evm" + "github.com/cosmos/ethermint/x/evm/types" + + abci "github.com/tendermint/tendermint/abci/types" +) + +type EvmTestSuite struct { + suite.Suite + + ctx sdk.Context + handler sdk.Handler + app *app.EthermintApp +} + +func (suite *EvmTestSuite) SetupTest() { + checkTx := false + + suite.app = app.Setup(checkTx) + suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()}) + suite.handler = evm.NewHandler(suite.app.EvmKeeper) +} + +func TestEvmTestSuite(t *testing.T) { + suite.Run(t, new(EvmTestSuite)) +} + +func (suite *EvmTestSuite) TestHandler_Logs() { + // Test contract: + + // pragma solidity ^0.5.1; + + // contract Test { + // event Hello(uint256 indexed world); + + // constructor() public { + // emit Hello(17); + // } + // } + + // { + // "linkReferences": {}, + // "object": "6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029", + // "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x11 PUSH32 0x775A94827B8FD9B519D36CD827093C664F93347070A554F65E4A6F56CD738898 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x35 DUP1 PUSH1 0x4B PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG1 PUSH6 0x627A7A723058 KECCAK256 PUSH13 0xAB665F0F557620554BB45ADF26 PUSH8 0x8D2BD349B8A4314 0xbd SELFDESTRUCT KECCAK256 0x5e 0xe8 DIFFICULTY 0xe EXTCODECOPY 0x24 STOP 0x29 ", + // "sourceMap": "25:119:0:-;;;90:52;8:9:-1;5:2;;;30:1;27;20:12;5:2;90:52:0;132:2;126:9;;;;;;;;;;25:119;;;;;;" + // } + + gasLimit := uint64(100000) + gasPrice := big.NewInt(1000000) + + priv, err := crypto.GenerateKey() + suite.Require().NoError(err, "failed to create key") + + bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029") + tx := types.NewMsgEthereumTx(1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode) + tx.Sign(big.NewInt(3), priv) + + result := suite.handler(suite.ctx, tx) + suite.Require().True(result.IsOK()) + + resultData, err := types.DecodeResultData(result.Data) + suite.Require().NoError(err, "failed to decode result data") + + suite.Require().Equal(len(resultData.Logs), 1) + suite.Require().Equal(len(resultData.Logs[0].Topics), 2) + + hash := []byte{1} + err = suite.app.EvmKeeper.SetTransactionLogs(suite.ctx, resultData.Logs, hash) + suite.Require().NoError(err, "failed to set logs") + + logs, err := suite.app.EvmKeeper.GetTransactionLogs(suite.ctx, hash) + suite.Require().NoError(err, "failed to get logs") + + suite.Require().Equal(logs, resultData.Logs) +} diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index f48c5bf1..111392b9 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -2,6 +2,8 @@ package keeper import ( "bytes" + "encoding/binary" + "errors" "fmt" "github.com/cosmos/cosmos-sdk/codec" @@ -9,7 +11,6 @@ import ( ethvm "github.com/ethereum/go-ethereum/core/vm" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/ethermint/x/evm/types" ethstate "github.com/ethereum/go-ethereum/core/state" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -24,35 +25,22 @@ type Keeper struct { cdc *codec.Codec // Store key required to update the block bloom filter mappings needed for the // Web3 API - storeKey sdk.StoreKey + blockKey sdk.StoreKey CommitStateDB *types.CommitStateDB - TxCount *count + TxCount int Bloom *big.Int } -// TODO: move to types -type count int - -func (c *count) Get() int { - return (int)(*c) -} - -func (c *count) Increment() { - *c++ -} - -func (c *count) Reset() { - *c = 0 -} - // NewKeeper generates new evm module keeper -func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey, - storeKey sdk.StoreKey, cdc *codec.Codec) Keeper { +func NewKeeper( + cdc *codec.Codec, blockKey, codeKey, storeKey sdk.StoreKey, + ak types.AccountKeeper, +) Keeper { return Keeper{ cdc: cdc, - storeKey: storeKey, - CommitStateDB: types.NewCommitStateDB(sdk.Context{}, ak, storageKey, codeKey), - TxCount: new(count), + blockKey: blockKey, + CommitStateDB: types.NewCommitStateDB(sdk.Context{}, codeKey, storeKey, ak), + TxCount: 0, Bloom: big.NewInt(0), } } @@ -64,21 +52,23 @@ func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey, // SetBlockHashMapping sets the mapping from block consensus hash to block height func (k *Keeper) SetBlockHashMapping(ctx sdk.Context, hash []byte, height int64) { - store := ctx.KVStore(k.storeKey) + store := ctx.KVStore(k.blockKey) if !bytes.Equal(hash, []byte{}) { - store.Set(hash, k.cdc.MustMarshalBinaryLengthPrefixed(height)) + bz := sdk.Uint64ToBigEndian(uint64(height)) + store.Set(hash, bz) } } // GetBlockHashMapping gets block height from block consensus hash func (k *Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (height int64) { - store := ctx.KVStore(k.storeKey) + store := ctx.KVStore(k.blockKey) bz := store.Get(hash) if bytes.Equal(bz, []byte{}) { panic(fmt.Errorf("block with hash %s not found", ethcmn.BytesToHash(hash))) } - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &height) - return + + height = int64(binary.BigEndian.Uint64(bz)) + return height } // ---------------------------------------------------------------------------- @@ -87,23 +77,54 @@ func (k *Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (height int64 // ---------------------------------------------------------------------------- // SetBlockBloomMapping sets the mapping from block height to bloom bits -func (k *Keeper) SetBlockBloomMapping(ctx sdk.Context, bloom ethtypes.Bloom, height int64) { - store := ctx.KVStore(k.storeKey) - heightHash := k.cdc.MustMarshalBinaryLengthPrefixed(height) - if !bytes.Equal(heightHash, []byte{}) { - store.Set(heightHash, bloom.Bytes()) +func (k *Keeper) SetBlockBloomMapping(ctx sdk.Context, bloom ethtypes.Bloom, height int64) error { + store := ctx.KVStore(k.blockKey) + bz := sdk.Uint64ToBigEndian(uint64(height)) + if len(bz) == 0 { + return fmt.Errorf("block with bloombits %v not found", bloom) } + + store.Set(types.BloomKey(bz), bloom.Bytes()) + return nil } // GetBlockBloomMapping gets bloombits from block height -func (k *Keeper) GetBlockBloomMapping(ctx sdk.Context, height int64) ethtypes.Bloom { - store := ctx.KVStore(k.storeKey) - heightHash := k.cdc.MustMarshalBinaryLengthPrefixed(height) - bloom := store.Get(heightHash) - if bytes.Equal(heightHash, []byte{}) { - panic(fmt.Errorf("block with bloombits %s not found", bloom)) +func (k *Keeper) GetBlockBloomMapping(ctx sdk.Context, height int64) (ethtypes.Bloom, error) { + store := ctx.KVStore(k.blockKey) + bz := sdk.Uint64ToBigEndian(uint64(height)) + if len(bz) == 0 { + return ethtypes.BytesToBloom([]byte{}), fmt.Errorf("block with height %d not found", height) } - return ethtypes.BytesToBloom(bloom) + + bloom := store.Get(types.BloomKey(bz)) + if len(bloom) == 0 { + return ethtypes.BytesToBloom([]byte{}), fmt.Errorf("block with bloombits %v not found", bloom) + } + + return ethtypes.BytesToBloom(bloom), nil +} + +// SetBlockLogs sets the transaction's logs in the KVStore +func (k *Keeper) SetTransactionLogs(ctx sdk.Context, logs []*ethtypes.Log, hash []byte) error { + store := ctx.KVStore(k.blockKey) + encLogs, err := types.EncodeLogs(logs) + if err != nil { + return err + } + store.Set(types.LogsKey(hash), encLogs) + + return nil +} + +// GetBlockLogs gets the logs for a transaction from the KVStore +func (k *Keeper) GetTransactionLogs(ctx sdk.Context, hash []byte) ([]*ethtypes.Log, error) { + store := ctx.KVStore(k.blockKey) + encLogs := store.Get(types.LogsKey(hash)) + if len(encLogs) == 0 { + return nil, errors.New("cannot get transaction logs") + } + + return types.DecodeLogs(encLogs) } // ---------------------------------------------------------------------------- @@ -225,13 +246,18 @@ func (k *Keeper) GetCommittedState(ctx sdk.Context, addr ethcmn.Address, hash et } // GetLogs calls CommitStateDB.GetLogs using the passed in context -func (k *Keeper) GetLogs(ctx sdk.Context, hash ethcmn.Hash) []*ethtypes.Log { - return k.CommitStateDB.WithContext(ctx).GetLogs(hash) +func (k *Keeper) GetLogs(ctx sdk.Context, hash ethcmn.Hash) ([]*ethtypes.Log, error) { + logs, err := k.CommitStateDB.WithContext(ctx).GetLogs(hash) + if err != nil { + return nil, err + } + + return logs, nil } -// Logs calls CommitStateDB.Logs using the passed in context -func (k *Keeper) Logs(ctx sdk.Context) []*ethtypes.Log { - return k.CommitStateDB.WithContext(ctx).Logs() +// AllLogs calls CommitStateDB.AllLogs using the passed in context +func (k *Keeper) AllLogs(ctx sdk.Context) []*ethtypes.Log { + return k.CommitStateDB.WithContext(ctx).AllLogs() } // GetRefund calls CommitStateDB.GetRefund using the passed in context @@ -264,13 +290,17 @@ func (k *Keeper) Commit(ctx sdk.Context, deleteEmptyObjects bool) (root ethcmn.H } // Finalise calls CommitStateDB.Finalise using the passed in context -func (k *Keeper) Finalise(ctx sdk.Context, deleteEmptyObjects bool) { - k.CommitStateDB.WithContext(ctx).Finalise(deleteEmptyObjects) +func (k *Keeper) Finalise(ctx sdk.Context, deleteEmptyObjects bool) error { + return k.CommitStateDB.WithContext(ctx).Finalise(deleteEmptyObjects) } // IntermediateRoot calls CommitStateDB.IntermediateRoot using the passed in context -func (k *Keeper) IntermediateRoot(ctx sdk.Context, deleteEmptyObjects bool) { - k.CommitStateDB.WithContext(ctx).IntermediateRoot(deleteEmptyObjects) +func (k *Keeper) IntermediateRoot(ctx sdk.Context, deleteEmptyObjects bool) error { + _, err := k.CommitStateDB.WithContext(ctx).IntermediateRoot(deleteEmptyObjects) + if err != nil { + return err + } + return nil } // ---------------------------------------------------------------------------- diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index f3eea05f..108a45f2 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -1,110 +1,78 @@ -package keeper +package keeper_test import ( "math/big" "testing" + "time" + + "github.com/stretchr/testify/suite" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/ethermint/types" - evmtypes "github.com/cosmos/ethermint/x/evm/types" + "github.com/cosmos/ethermint/app" + "github.com/cosmos/ethermint/x/evm/keeper" ethcmn "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - tmlog "github.com/tendermint/tendermint/libs/log" - dbm "github.com/tendermint/tm-db" ) -var ( - address = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1") +var address = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1") - accKey = sdk.NewKVStoreKey("acc") - storageKey = sdk.NewKVStoreKey(evmtypes.EvmStoreKey) - codeKey = sdk.NewKVStoreKey(evmtypes.EvmCodeKey) - blockKey = sdk.NewKVStoreKey(evmtypes.EvmBlockKey) +type KeeperTestSuite struct { + suite.Suite - logger = tmlog.NewNopLogger() -) - -func newTestCodec() *codec.Codec { - cdc := codec.New() - - evmtypes.RegisterCodec(cdc) - types.RegisterCodec(cdc) - auth.RegisterCodec(cdc) - sdk.RegisterCodec(cdc) - codec.RegisterCrypto(cdc) - - return cdc + ctx sdk.Context + querier sdk.Querier + app *app.EthermintApp } -func TestDBStorage(t *testing.T) { - // create logger, codec and root multi-store - cdc := newTestCodec() +func (suite *KeeperTestSuite) SetupTest() { + checkTx := false - // The ParamsKeeper handles parameter storage for the application - keyParams := sdk.NewKVStoreKey(params.StoreKey) - tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) - paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) - // Set specific supspaces - authSubspace := paramsKeeper.Subspace(auth.DefaultParamspace) - ak := auth.NewAccountKeeper(cdc, accKey, authSubspace, types.ProtoBaseAccount) - ek := NewKeeper(ak, storageKey, codeKey, blockKey, cdc) + suite.app = app.Setup(checkTx) + suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()}) + suite.querier = keeper.NewQuerier(suite.app.EvmKeeper) +} - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - // mount stores - keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey, blockKey} - for _, key := range keys { - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, nil) - } - - // load latest version (root) - err := cms.LoadLatestVersion() - require.NoError(t, err) - - // First execution - ms := cms.CacheMultiStore() - ctx := sdk.NewContext(ms, abci.Header{}, false, logger) - ctx = ctx.WithBlockHeight(1) +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} +func (suite *KeeperTestSuite) TestDBStorage() { // Perform state transitions - ek.SetBalance(ctx, address, big.NewInt(5)) - ek.SetNonce(ctx, address, 4) - ek.SetState(ctx, address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3")) - ek.SetCode(ctx, address, []byte{0x1}) + suite.app.EvmKeeper.CreateAccount(suite.ctx, address) + suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(5)) + suite.app.EvmKeeper.SetNonce(suite.ctx, address, 4) + suite.app.EvmKeeper.SetState(suite.ctx, address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3")) + suite.app.EvmKeeper.SetCode(suite.ctx, address, []byte{0x1}) // Test block hash mapping functionality - ek.SetBlockHashMapping(ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"), 7) - ek.SetBlockHashMapping(ctx, []byte{0x43, 0x32}, 8) + suite.app.EvmKeeper.SetBlockHashMapping(suite.ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"), 7) + suite.app.EvmKeeper.SetBlockHashMapping(suite.ctx, []byte{0x43, 0x32}, 8) // Test block height mapping functionality testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3}) - ek.SetBlockBloomMapping(ctx, testBloom, 4) + err := suite.app.EvmKeeper.SetBlockBloomMapping(suite.ctx, testBloom, 4) + suite.Require().NoError(err, "failed to set block bloom mapping") // Get those state transitions - require.Equal(t, ek.GetBalance(ctx, address).Cmp(big.NewInt(5)), 0) - require.Equal(t, ek.GetNonce(ctx, address), uint64(4)) - require.Equal(t, ek.GetState(ctx, address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3")) - require.Equal(t, ek.GetCode(ctx, address), []byte{0x1}) + suite.Require().Equal(suite.app.EvmKeeper.GetBalance(suite.ctx, address).Cmp(big.NewInt(5)), 0) + suite.Require().Equal(suite.app.EvmKeeper.GetNonce(suite.ctx, address), uint64(4)) + suite.Require().Equal(suite.app.EvmKeeper.GetState(suite.ctx, address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3")) + suite.Require().Equal(suite.app.EvmKeeper.GetCode(suite.ctx, address), []byte{0x1}) - require.Equal(t, ek.GetBlockHashMapping(ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68")), int64(7)) - require.Equal(t, ek.GetBlockHashMapping(ctx, []byte{0x43, 0x32}), int64(8)) - - require.Equal(t, ek.GetBlockBloomMapping(ctx, 4), testBloom) + suite.Require().Equal(suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68")), int64(7)) + suite.Require().Equal(suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, []byte{0x43, 0x32}), int64(8)) + bloom, err := suite.app.EvmKeeper.GetBlockBloomMapping(suite.ctx, 4) + suite.Require().NoError(err) + suite.Require().Equal(bloom, testBloom) // commit stateDB - _, err = ek.Commit(ctx, false) - require.NoError(t, err, "failed to commit StateDB") + _, err = suite.app.EvmKeeper.Commit(suite.ctx, false) + suite.Require().NoError(err, "failed to commit StateDB") // simulate BaseApp EndBlocker commitment - ms.Write() - cms.Commit() + suite.app.Commit() } diff --git a/x/evm/keeper/querier.go b/x/evm/keeper/querier.go index 1a77d5c3..8509e9dc 100644 --- a/x/evm/keeper/querier.go +++ b/x/evm/keeper/querier.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "strconv" "github.com/cosmos/cosmos-sdk/codec" @@ -15,7 +16,7 @@ import ( // NewQuerier is the module level router for state queries func NewQuerier(keeper Keeper) sdk.Querier { - return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { switch path[0] { case types.QueryProtocolVersion: return queryProtocolVersion(keeper) @@ -48,140 +49,146 @@ func NewQuerier(keeper Keeper) sdk.Querier { func queryProtocolVersion(keeper Keeper) ([]byte, sdk.Error) { vers := version.ProtocolVersion - res, err := codec.MarshalJSONIndent(keeper.cdc, hexutil.Uint(vers)) + bz, err := codec.MarshalJSONIndent(keeper.cdc, hexutil.Uint(vers)) if err != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(err.Error()) } - return res, nil + return bz, nil } func queryBalance(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { addr := ethcmn.HexToAddress(path[1]) balance := keeper.GetBalance(ctx, addr) - bRes := types.QueryResBalance{Balance: utils.MarshalBigInt(balance)} - res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) + res := types.QueryResBalance{Balance: utils.MarshalBigInt(balance)} + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return res, nil + return bz, nil } func queryBlockNumber(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { num := ctx.BlockHeight() bnRes := types.QueryResBlockNumber{Number: num} - res, err := codec.MarshalJSONIndent(keeper.cdc, bnRes) + bz, err := codec.MarshalJSONIndent(keeper.cdc, bnRes) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return res, nil + return bz, nil } func queryStorage(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { addr := ethcmn.HexToAddress(path[1]) key := ethcmn.HexToHash(path[2]) val := keeper.GetState(ctx, addr, key) - bRes := types.QueryResStorage{Value: val.Bytes()} - res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) + res := types.QueryResStorage{Value: val.Bytes()} + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return res, nil + return bz, nil } func queryCode(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { addr := ethcmn.HexToAddress(path[1]) code := keeper.GetCode(ctx, addr) - cRes := types.QueryResCode{Code: code} - res, err := codec.MarshalJSONIndent(keeper.cdc, cRes) + res := types.QueryResCode{Code: code} + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return res, nil + return bz, nil } func queryNonce(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { addr := ethcmn.HexToAddress(path[1]) nonce := keeper.GetNonce(ctx, addr) nRes := types.QueryResNonce{Nonce: nonce} - res, err := codec.MarshalJSONIndent(keeper.cdc, nRes) + bz, err := codec.MarshalJSONIndent(keeper.cdc, nRes) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return res, nil + return bz, nil } func queryHashToHeight(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { blockHash := ethcmn.FromHex(path[1]) blockNumber := keeper.GetBlockHashMapping(ctx, blockHash) - bRes := types.QueryResBlockNumber{Number: blockNumber} - res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) + res := types.QueryResBlockNumber{Number: blockNumber} + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return res, nil + return bz, nil } func queryBlockLogsBloom(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { num, err := strconv.ParseInt(path[1], 10, 64) if err != nil { - panic("could not unmarshall block number: " + err.Error()) + return nil, sdk.ErrInternal(fmt.Sprintf("could not unmarshall block number: %s", err.Error())) } - bloom := keeper.GetBlockBloomMapping(ctx, num) - - bRes := types.QueryBloomFilter{Bloom: bloom} - res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) + bloom, err := keeper.GetBlockBloomMapping(ctx, num) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(fmt.Sprintf("failed to get block bloom mapping: %s", err.Error())) } - return res, nil + res := types.QueryBloomFilter{Bloom: bloom} + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) + if err != nil { + return nil, sdk.ErrInternal(err.Error()) + } + + return bz, nil } func queryTxLogs(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { txHash := ethcmn.HexToHash(path[1]) - logs := keeper.GetLogs(ctx, txHash) - - bRes := types.QueryETHLogs{Logs: logs} - res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) + logs, err := keeper.GetLogs(ctx, txHash) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return res, nil + res := types.QueryETHLogs{Logs: logs} + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) + if err != nil { + return nil, sdk.ErrInternal(err.Error()) + } + + return bz, nil } func queryLogs(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { - logs := keeper.Logs(ctx) + logs := keeper.AllLogs(ctx) - lRes := types.QueryETHLogs{Logs: logs} - l, err := codec.MarshalJSONIndent(keeper.cdc, lRes) + res := types.QueryETHLogs{Logs: logs} + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return l, nil + return bz, nil } func queryAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { addr := ethcmn.HexToAddress(path[1]) so := keeper.GetOrNewStateObject(ctx, addr) - lRes := types.QueryResAccount{ + res := types.QueryResAccount{ Balance: utils.MarshalBigInt(so.Balance()), CodeHash: so.CodeHash(), Nonce: so.Nonce(), } - l, err := codec.MarshalJSONIndent(keeper.cdc, lRes) + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) if err != nil { - panic("could not marshal result to JSON: " + err.Error()) + return nil, sdk.ErrInternal(err.Error()) } - return l, nil + return bz, nil } diff --git a/x/evm/module.go b/x/evm/module.go index b8ef1b1b..7b24e01b 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -2,19 +2,20 @@ package evm import ( "encoding/json" - "math/big" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/ethermint/x/evm/client/cli" "github.com/cosmos/ethermint/x/evm/keeper" "github.com/cosmos/ethermint/x/evm/types" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/gorilla/mux" - "github.com/spf13/cobra" - abci "github.com/tendermint/tendermint/abci/types" ) var _ module.AppModuleBasic = AppModuleBasic{} @@ -107,33 +108,13 @@ func (am AppModule) NewQuerierHandler() sdk.Querier { } // BeginBlock function for module at start of each block -func (am AppModule) BeginBlock(ctx sdk.Context, bl abci.RequestBeginBlock) { - // Consider removing this when using evm as module without web3 API - bloom := ethtypes.BytesToBloom(am.keeper.Bloom.Bytes()) - am.keeper.SetBlockBloomMapping(ctx, bloom, bl.Header.GetHeight()-1) - am.keeper.SetBlockHashMapping(ctx, bl.Header.LastBlockId.GetHash(), bl.Header.GetHeight()-1) - am.keeper.Bloom = big.NewInt(0) - am.keeper.TxCount.Reset() +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlock(am.keeper, ctx, req) } // EndBlock function for module at end of block -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - // Gas costs are handled within msg handler so costs should be ignored - ebCtx := ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) - - // Update account balances before committing other parts of state - am.keeper.CommitStateDB.UpdateAccounts() - - // Commit state objects to KV store - _, err := am.keeper.CommitStateDB.WithContext(ebCtx).Commit(true) - if err != nil { - panic(err) - } - - // Clear accounts cache after account data has been committed - am.keeper.CommitStateDB.ClearStateObjects() - - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate { + return EndBlock(am.keeper, ctx, req) } // InitGenesis instantiates the genesis state diff --git a/x/evm/types/codec.go b/x/evm/types/codec.go index a657b845..39efba39 100644 --- a/x/evm/types/codec.go +++ b/x/evm/types/codec.go @@ -9,14 +9,13 @@ var ModuleCdc = codec.New() func init() { cdc := codec.New() - codec.RegisterCrypto(cdc) - ModuleCdc = cdc.Seal() } // RegisterCodec registers concrete types and interfaces on the given codec. func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(&EthereumTxMsg{}, "ethermint/MsgEthereumTx", nil) - cdc.RegisterConcrete(&EmintMsg{}, "ethermint/MsgEmint", nil) + cdc.RegisterConcrete(MsgEthereumTx{}, "ethermint/MsgEthereumTx", nil) + cdc.RegisterConcrete(MsgEthermint{}, "ethermint/MsgEthermint", nil) + cdc.RegisterConcrete(EncodableTxData{}, "ethermint/EncodableTxData", nil) } diff --git a/x/evm/types/dump.go b/x/evm/types/dump.go deleted file mode 100644 index ed9fd66b..00000000 --- a/x/evm/types/dump.go +++ /dev/null @@ -1,12 +0,0 @@ -package types - -import ( - ethstate "github.com/ethereum/go-ethereum/core/state" -) - -// RawDump returns a raw state dump. -// -// TODO: Implement if we need it, especially for the RPC API. -func (csdb *CommitStateDB) RawDump() ethstate.Dump { - return ethstate.Dump{} -} diff --git a/x/evm/types/emint_msg.go b/x/evm/types/emint_msg.go index 41be900e..616de8cf 100644 --- a/x/evm/types/emint_msg.go +++ b/x/evm/types/emint_msg.go @@ -4,21 +4,22 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ethermint/types" ethcmn "github.com/ethereum/go-ethereum/common" ) var ( - _ sdk.Msg = EmintMsg{} + _ sdk.Msg = MsgEthermint{} ) const ( - // TypeEmintMsg defines the type string of Emint message - TypeEmintMsg = "emint_tx" + // TypeMsgEthermint defines the type string of Ethermint message + TypeMsgEthermint = "ethermint" ) -// EmintMsg implements a cosmos equivalent structure for Ethereum transactions -type EmintMsg struct { +// MsgEthermint implements a cosmos equivalent structure for Ethereum transactions +type MsgEthermint struct { AccountNonce uint64 `json:"nonce"` Price sdk.Int `json:"gasPrice"` GasLimit uint64 `json:"gas"` @@ -30,12 +31,12 @@ type EmintMsg struct { From sdk.AccAddress `json:"from"` } -// NewEmintMsg returns a reference to a new Ethermint transaction -func NewEmintMsg( +// NewMsgEthermint returns a reference to a new Ethermint transaction +func NewMsgEthermint( nonce uint64, to *sdk.AccAddress, amount sdk.Int, gasLimit uint64, gasPrice sdk.Int, payload []byte, from sdk.AccAddress, -) EmintMsg { - return EmintMsg{ +) MsgEthermint { + return MsgEthermint{ AccountNonce: nonce, Price: gasPrice, GasLimit: gasLimit, @@ -47,18 +48,18 @@ func NewEmintMsg( } // Route should return the name of the module -func (msg EmintMsg) Route() string { return RouterKey } +func (msg MsgEthermint) Route() string { return RouterKey } // Type returns the action of the message -func (msg EmintMsg) Type() string { return TypeEmintMsg } +func (msg MsgEthermint) Type() string { return TypeMsgEthermint } // GetSignBytes encodes the message for signing -func (msg EmintMsg) GetSignBytes() []byte { +func (msg MsgEthermint) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } // ValidateBasic runs stateless checks on the message -func (msg EmintMsg) ValidateBasic() sdk.Error { +func (msg MsgEthermint) ValidateBasic() sdk.Error { if msg.Price.Sign() != 1 { return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Price)) } @@ -72,13 +73,13 @@ func (msg EmintMsg) ValidateBasic() sdk.Error { } // GetSigners defines whose signature is required -func (msg EmintMsg) GetSigners() []sdk.AccAddress { +func (msg MsgEthermint) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.From} } // To returns the recipient address of the transaction. It returns nil if the // transaction is a contract creation. -func (msg EmintMsg) To() *ethcmn.Address { +func (msg MsgEthermint) To() *ethcmn.Address { if msg.Recipient == nil { return nil } diff --git a/x/evm/types/emint_msg_test.go b/x/evm/types/emint_msg_test.go index f28f1947..d1886cea 100644 --- a/x/evm/types/emint_msg_test.go +++ b/x/evm/types/emint_msg_test.go @@ -8,19 +8,19 @@ import ( "github.com/tendermint/tendermint/crypto/secp256k1" ) -func TestEmintMsg(t *testing.T) { +func TestMsgEthermint(t *testing.T) { addr := newSdkAddress() fromAddr := newSdkAddress() - msg := NewEmintMsg(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr) + msg := NewMsgEthermint(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr) require.NotNil(t, msg) require.Equal(t, msg.Recipient, &addr) require.Equal(t, msg.Route(), RouterKey) - require.Equal(t, msg.Type(), TypeEmintMsg) + require.Equal(t, msg.Type(), TypeMsgEthermint) } -func TestEmintMsgValidation(t *testing.T) { +func TestMsgEthermintValidation(t *testing.T) { testCases := []struct { nonce uint64 to *sdk.AccAddress @@ -38,7 +38,7 @@ func TestEmintMsgValidation(t *testing.T) { } for i, tc := range testCases { - msg := NewEmintMsg(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload, tc.from) + msg := NewMsgEthermint(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload, tc.from) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", i) @@ -52,13 +52,13 @@ func TestEmintEncodingAndDecoding(t *testing.T) { addr := newSdkAddress() fromAddr := newSdkAddress() - msg := NewEmintMsg(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr) + msg := NewMsgEthermint(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr) - raw, err := cdc.MarshalBinaryBare(msg) + raw, err := ModuleCdc.MarshalBinaryBare(msg) require.NoError(t, err) - var msg2 EmintMsg - err = cdc.UnmarshalBinaryBare(raw, &msg2) + var msg2 MsgEthermint + err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2) require.NoError(t, err) require.Equal(t, msg.AccountNonce, msg2.AccountNonce) diff --git a/x/evm/types/events.go b/x/evm/types/events.go new file mode 100644 index 00000000..e5dca425 --- /dev/null +++ b/x/evm/types/events.go @@ -0,0 +1,11 @@ +package types + +// Evm module events +const ( + EventTypeEthermint = TypeMsgEthermint + EventTypeEthereumTx = TypeMsgEthereumTx + + AttributeKeyContractAddress = "contract" + AttributeKeyRecipient = "recipient" + AttributeValueCategory = ModuleName +) diff --git a/x/evm/types/expected_keepers.go b/x/evm/types/expected_keepers.go new file mode 100644 index 00000000..5bcf05ee --- /dev/null +++ b/x/evm/types/expected_keepers.go @@ -0,0 +1,14 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" +) + +// AccountKeeper defines the expected account keeper interface +type AccountKeeper interface { + NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authexported.Account + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account + SetAccount(ctx sdk.Context, account authexported.Account) + RemoveAccount(ctx sdk.Context, account authexported.Account) +} diff --git a/x/evm/types/genesis.go b/x/evm/types/genesis.go index 30320a39..d3da7068 100644 --- a/x/evm/types/genesis.go +++ b/x/evm/types/genesis.go @@ -1,7 +1,7 @@ package types import ( - "fmt" + "errors" "math/big" "github.com/cosmos/ethermint/types" @@ -28,10 +28,10 @@ type ( func ValidateGenesis(data GenesisState) error { for _, acct := range data.Accounts { if len(acct.Address.Bytes()) == 0 { - return fmt.Errorf("invalid GenesisAccount Error: Missing Address") + return errors.New("invalid GenesisAccount: address cannot be empty") } if acct.Balance == nil { - return fmt.Errorf("invalid GenesisAccount Error: Missing Balance") + return errors.New("invalid GenesisAccount: balance cannot be empty") } } return nil diff --git a/x/evm/types/key.go b/x/evm/types/key.go index 839a110b..cd1501ee 100644 --- a/x/evm/types/key.go +++ b/x/evm/types/key.go @@ -4,13 +4,24 @@ const ( // ModuleName string name of module ModuleName = "evm" - // EvmStoreKey key for ethereum storage data - EvmStoreKey = "evmstore" - // EvmCodeKey key for ethereum code data - EvmCodeKey = "evmcode" - // EvmBlockKey key for ethereum block data - EvmBlockKey = "evmblock" + // StoreKey key for ethereum storage data (StateDB) + StoreKey = ModuleName + // CodeKey key for ethereum code data + CodeKey = ModuleName + "code" + // BlockKey key + BlockKey = ModuleName + "block" // RouterKey uses module name for routing RouterKey = ModuleName ) + +var bloomPrefix = []byte("bloom") +var logsPrefix = []byte("logs") + +func BloomKey(key []byte) []byte { + return append(bloomPrefix, key...) +} + +func LogsKey(key []byte) []byte { + return append(logsPrefix, key...) +} diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 980bdd6f..2300ce1f 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -19,21 +19,20 @@ import ( ) var ( - _ sdk.Msg = EthereumTxMsg{} - _ sdk.Tx = EthereumTxMsg{} + _ sdk.Msg = MsgEthereumTx{} + _ sdk.Tx = MsgEthereumTx{} ) var big8 = big.NewInt(8) // message type and route constants const ( - TypeEthereumTxMsg = "ethereum_tx" - RouteEthereumTxMsg = RouterKey + TypeMsgEthereumTx = "ethereum" ) -// EthereumTxMsg encapsulates an Ethereum transaction as an SDK message. +// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message. type ( - EthereumTxMsg struct { + MsgEthereumTx struct { Data TxData // caches @@ -69,28 +68,28 @@ type ( } ) -// NewEthereumTxMsg returns a reference to a new Ethereum transaction message. -func NewEthereumTxMsg( +// NewMsgEthereumTx returns a reference to a new Ethereum transaction message. +func NewMsgEthereumTx( nonce uint64, to *ethcmn.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte, -) *EthereumTxMsg { +) MsgEthereumTx { - return newEthereumTxMsg(nonce, to, amount, gasLimit, gasPrice, payload) + return newMsgEthereumTx(nonce, to, amount, gasLimit, gasPrice, payload) } -// NewEthereumTxMsgContract returns a reference to a new Ethereum transaction +// NewMsgEthereumTxContract returns a reference to a new Ethereum transaction // message designated for contract creation. -func NewEthereumTxMsgContract( +func NewMsgEthereumTxContract( nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte, -) *EthereumTxMsg { +) MsgEthereumTx { - return newEthereumTxMsg(nonce, nil, amount, gasLimit, gasPrice, payload) + return newMsgEthereumTx(nonce, nil, amount, gasLimit, gasPrice, payload) } -func newEthereumTxMsg( +func newMsgEthereumTx( nonce uint64, to *ethcmn.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte, -) *EthereumTxMsg { +) MsgEthereumTx { if len(payload) > 0 { payload = ethcmn.CopyBytes(payload) @@ -115,20 +114,20 @@ func newEthereumTxMsg( txData.Price.Set(gasPrice) } - return &EthereumTxMsg{Data: txData} + return MsgEthereumTx{Data: txData} } -// Route returns the route value of an EthereumTxMsg. -func (msg EthereumTxMsg) Route() string { return RouteEthereumTxMsg } +// Route returns the route value of an MsgEthereumTx. +func (msg MsgEthereumTx) Route() string { return RouterKey } -// Type returns the type value of an EthereumTxMsg. -func (msg EthereumTxMsg) Type() string { return TypeEthereumTxMsg } +// Type returns the type value of an MsgEthereumTx. +func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx } // ValidateBasic implements the sdk.Msg interface. It performs basic validation -// checks of a Transaction. If returns an sdk.Error if validation fails. -func (msg EthereumTxMsg) ValidateBasic() sdk.Error { +// checks of a Transaction. If returns an error if validation fails. +func (msg MsgEthereumTx) ValidateBasic() sdk.Error { if msg.Data.Price.Sign() != 1 { - return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Data.Price)) + return types.ErrInvalidValue(fmt.Sprintf("price must be positive: %x", msg.Data.Price)) } // Amount can be 0 @@ -141,16 +140,12 @@ func (msg EthereumTxMsg) ValidateBasic() sdk.Error { // To returns the recipient address of the transaction. It returns nil if the // transaction is a contract creation. -func (msg EthereumTxMsg) To() *ethcmn.Address { - if msg.Data.Recipient == nil { - return nil - } - +func (msg MsgEthereumTx) To() *ethcmn.Address { return msg.Data.Recipient } -// GetMsgs returns a single EthereumTxMsg as an sdk.Msg. -func (msg EthereumTxMsg) GetMsgs() []sdk.Msg { +// GetMsgs returns a single MsgEthereumTx as an sdk.Msg. +func (msg MsgEthereumTx) GetMsgs() []sdk.Msg { return []sdk.Msg{msg} } @@ -159,7 +154,7 @@ func (msg EthereumTxMsg) GetMsgs() []sdk.Msg { // // NOTE: This method cannot be used as a chain ID is needed to recover the signer // from the signature. Use 'VerifySig' instead. -func (msg EthereumTxMsg) GetSigners() []sdk.AccAddress { +func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress { panic("must use 'VerifySig' with a chain ID to get the signer") } @@ -168,13 +163,13 @@ func (msg EthereumTxMsg) GetSigners() []sdk.AccAddress { // // NOTE: This method cannot be used as a chain ID is needed to create valid bytes // to sign over. Use 'RLPSignBytes' instead. -func (msg EthereumTxMsg) GetSignBytes() []byte { +func (msg MsgEthereumTx) GetSignBytes() []byte { panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign") } // RLPSignBytes returns the RLP hash of an Ethereum transaction message with a // given chainID used for signing. -func (msg EthereumTxMsg) RLPSignBytes(chainID *big.Int) ethcmn.Hash { +func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash { return rlpHash([]interface{}{ msg.Data.AccountNonce, msg.Data.Price, @@ -187,12 +182,12 @@ func (msg EthereumTxMsg) RLPSignBytes(chainID *big.Int) ethcmn.Hash { } // EncodeRLP implements the rlp.Encoder interface. -func (msg *EthereumTxMsg) EncodeRLP(w io.Writer) error { +func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error { return rlp.Encode(w, &msg.Data) } // DecodeRLP implements the rlp.Decoder interface. -func (msg *EthereumTxMsg) DecodeRLP(s *rlp.Stream) error { +func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error { _, size, _ := s.Kind() err := s.Decode(&msg.Data) @@ -204,7 +199,7 @@ func (msg *EthereumTxMsg) DecodeRLP(s *rlp.Stream) error { } // Hash hashes the RLP encoding of a transaction. -func (msg *EthereumTxMsg) Hash() ethcmn.Hash { +func (msg *MsgEthereumTx) Hash() ethcmn.Hash { if hash := msg.hash.Load(); hash != nil { return hash.(ethcmn.Hash) } @@ -219,7 +214,7 @@ func (msg *EthereumTxMsg) Hash() ethcmn.Hash { // takes a private key and chainID to sign an Ethereum transaction according to // EIP155 standard. It mutates the transaction as it populates the V, R, S // fields of the Transaction's Signature. -func (msg *EthereumTxMsg) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) { +func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) { txHash := msg.RLPSignBytes(chainID) sig, err := ethcrypto.Sign(txHash[:], priv) @@ -252,7 +247,7 @@ func (msg *EthereumTxMsg) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) { // VerifySig attempts to verify a Transaction's signature for a given chainID. // A derived address is returned upon success or an error if recovery fails. -func (msg *EthereumTxMsg) VerifySig(chainID *big.Int) (ethcmn.Address, error) { +func (msg *MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) { signer := ethtypes.NewEIP155Signer(chainID) if sc := msg.from.Load(); sc != nil { @@ -284,19 +279,19 @@ func (msg *EthereumTxMsg) VerifySig(chainID *big.Int) (ethcmn.Address, error) { } // Cost returns amount + gasprice * gaslimit. -func (msg EthereumTxMsg) Cost() *big.Int { +func (msg MsgEthereumTx) Cost() *big.Int { total := msg.Fee() total.Add(total, msg.Data.Amount) return total } // Fee returns gasprice * gaslimit. -func (msg EthereumTxMsg) Fee() *big.Int { +func (msg MsgEthereumTx) Fee() *big.Int { return new(big.Int).Mul(msg.Data.Price, new(big.Int).SetUint64(msg.Data.GasLimit)) } // ChainID returns which chain id this transaction was signed for (if at all) -func (msg *EthereumTxMsg) ChainID() *big.Int { +func (msg *MsgEthereumTx) ChainID() *big.Int { return deriveChainID(msg.Data.V) } @@ -317,7 +312,7 @@ func deriveChainID(v *big.Int) *big.Int { // Auxiliary // TxDecoder returns an sdk.TxDecoder that can decode both auth.StdTx and -// EthereumTxMsg transactions. +// MsgEthereumTx transactions. func TxDecoder(cdc *codec.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { var tx sdk.Tx @@ -328,7 +323,6 @@ func TxDecoder(cdc *codec.Codec) sdk.TxDecoder { err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) if err != nil { - fmt.Println(err.Error()) return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error()) } diff --git a/x/evm/types/msg_encoding.go b/x/evm/types/msg_encoding.go index 1284d3a5..ddbf16da 100644 --- a/x/evm/types/msg_encoding.go +++ b/x/evm/types/msg_encoding.go @@ -1,23 +1,11 @@ package types import ( - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/ethermint/utils" ethcmn "github.com/ethereum/go-ethereum/common" ) -var cdc = codec.New() - -func init() { - RegisterAmino(cdc) -} - -// RegisterAmino registers all crypto related types in the given (amino) codec. -func RegisterAmino(cdc *codec.Codec) { - cdc.RegisterConcrete(EncodableTxData{}, "ethermint/EncodedMessage", nil) -} - // EncodableTxData implements the Ethereum transaction data structure. It is used // solely as intended in Ethereum abiding by the protocol. type EncodableTxData struct { @@ -38,12 +26,12 @@ type EncodableTxData struct { } func marshalAmino(td EncodableTxData) (string, error) { - bz, err := cdc.MarshalBinaryBare(td) + bz, err := ModuleCdc.MarshalBinaryBare(td) return string(bz), err } func unmarshalAmino(td *EncodableTxData, text string) error { - return cdc.UnmarshalBinaryBare([]byte(text), td) + return ModuleCdc.UnmarshalBinaryBare([]byte(text), td) } // MarshalAmino defines custom encoding scheme for TxData diff --git a/x/evm/types/msg_test.go b/x/evm/types/msg_test.go index a7654d24..c25281db 100644 --- a/x/evm/types/msg_test.go +++ b/x/evm/types/msg_test.go @@ -18,17 +18,17 @@ import ( func TestMsgEthereumTx(t *testing.T) { addr := GenerateEthAddress() - msg1 := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) + msg1 := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test")) require.NotNil(t, msg1) require.Equal(t, *msg1.Data.Recipient, addr) - msg2 := NewEthereumTxMsgContract(0, nil, 100000, nil, []byte("test")) + msg2 := NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test")) require.NotNil(t, msg2) require.Nil(t, msg2.Data.Recipient) - msg3 := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) - require.Equal(t, msg3.Route(), RouteEthereumTxMsg) - require.Equal(t, msg3.Type(), TypeEthereumTxMsg) + msg3 := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test")) + require.Equal(t, msg3.Route(), RouterKey) + require.Equal(t, msg3.Type(), TypeMsgEthereumTx) require.Panics(t, func() { msg3.GetSigners() }) require.Panics(t, func() { msg3.GetSignBytes() }) } @@ -49,7 +49,7 @@ func TestMsgEthereumTxValidation(t *testing.T) { } for i, tc := range testCases { - msg := NewEthereumTxMsg(tc.nonce, &tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload) + msg := NewMsgEthereumTx(tc.nonce, &tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", i) @@ -63,26 +63,26 @@ func TestMsgEthereumTxRLPSignBytes(t *testing.T) { addr := ethcmn.BytesToAddress([]byte("test_address")) chainID := big.NewInt(3) - msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) + msg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test")) hash := msg.RLPSignBytes(chainID) require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash)) } func TestMsgEthereumTxRLPEncode(t *testing.T) { addr := ethcmn.BytesToAddress([]byte("test_address")) - msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) + msg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test")) - raw, err := rlp.EncodeToBytes(msg) + raw, err := rlp.EncodeToBytes(&msg) require.NoError(t, err) require.Equal(t, ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080"), raw) } func TestMsgEthereumTxRLPDecode(t *testing.T) { - var msg EthereumTxMsg + var msg MsgEthereumTx raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080") addr := ethcmn.BytesToAddress([]byte("test_address")) - expectedMsg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) + expectedMsg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test")) err := rlp.Decode(bytes.NewReader(raw), &msg) require.NoError(t, err) @@ -91,7 +91,7 @@ func TestMsgEthereumTxRLPDecode(t *testing.T) { func TestMsgEthereumTxHash(t *testing.T) { addr := ethcmn.BytesToAddress([]byte("test_address")) - msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) + msg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test")) hash := msg.Hash() require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash)) @@ -106,7 +106,7 @@ func TestMsgEthereumTxSig(t *testing.T) { addr2 := ethcmn.BytesToAddress(priv2.PubKey().Address().Bytes()) // require valid signature passes validation - msg := NewEthereumTxMsg(0, &addr1, nil, 100000, nil, []byte("test")) + msg := NewMsgEthereumTx(0, &addr1, nil, 100000, nil, []byte("test")) msg.Sign(chainID, priv1.ToECDSA()) signer, err := msg.VerifySig(chainID) @@ -115,7 +115,7 @@ func TestMsgEthereumTxSig(t *testing.T) { require.NotEqual(t, addr2, signer) // require invalid chain ID fail validation - msg = NewEthereumTxMsg(0, &addr1, nil, 100000, nil, []byte("test")) + msg = NewMsgEthereumTx(0, &addr1, nil, 100000, nil, []byte("test")) msg.Sign(chainID, priv1.ToECDSA()) signer, err = msg.VerifySig(big.NewInt(4)) @@ -125,7 +125,7 @@ func TestMsgEthereumTxSig(t *testing.T) { func TestMsgEthereumTxAmino(t *testing.T) { addr := GenerateEthAddress() - msg := NewEthereumTxMsg(5, &addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test")) + msg := NewMsgEthereumTx(5, &addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test")) msg.Data.V = big.NewInt(1) msg.Data.R = big.NewInt(2) @@ -134,7 +134,7 @@ func TestMsgEthereumTxAmino(t *testing.T) { raw, err := ModuleCdc.MarshalBinaryBare(msg) require.NoError(t, err) - var msg2 EthereumTxMsg + var msg2 MsgEthereumTx err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2) require.NoError(t, err) diff --git a/x/evm/types/state_object.go b/x/evm/types/state_object.go index 2ae43d28..ce78f82a 100644 --- a/x/evm/types/state_object.go +++ b/x/evm/types/state_object.go @@ -73,20 +73,21 @@ type ( } ) -func newObject(db *CommitStateDB, accProto authexported.Account) *stateObject { - acc, ok := accProto.(*types.Account) +func newStateObject(db *CommitStateDB, accProto authexported.Account) *stateObject { + ethermintAccount, ok := accProto.(*types.Account) if !ok { panic(fmt.Sprintf("invalid account type for state object: %T", accProto)) } - if acc.CodeHash == nil { - acc.CodeHash = emptyCodeHash + // set empty code hash + if ethermintAccount.CodeHash == nil { + ethermintAccount.CodeHash = emptyCodeHash } return &stateObject{ stateDB: db, - account: acc, - address: ethcmn.BytesToAddress(acc.Address.Bytes()), + account: ethermintAccount, + address: ethcmn.BytesToAddress(ethermintAccount.GetAddress().Bytes()), originStorage: make(types.Storage), dirtyStorage: make(types.Storage), } @@ -188,7 +189,7 @@ func (so *stateObject) setBalance(amount sdk.Int) { so.account.SetBalance(amount) } -// SetNonce sets the state object's nonce (sequence number). +// SetNonce sets the state object's nonce (i.e sequence number of the account). func (so *stateObject) SetNonce(nonce uint64) { so.stateDB.journal.append(nonceChange{ account: &so.address, @@ -199,6 +200,9 @@ func (so *stateObject) SetNonce(nonce uint64) { } func (so *stateObject) setNonce(nonce uint64) { + if so.account == nil { + panic("state object account is empty") + } so.account.Sequence = nonce } @@ -216,7 +220,7 @@ func (so *stateObject) markSuicided() { // commitState commits all dirty storage to a KVStore. func (so *stateObject) commitState() { ctx := so.stateDB.ctx - store := ctx.KVStore(so.stateDB.storageKey) + store := ctx.KVStore(so.stateDB.storeKey) for key, value := range so.dirtyStorage { delete(so.dirtyStorage, key) @@ -258,22 +262,32 @@ func (so stateObject) Address() ethcmn.Address { // Balance returns the state object's current balance. func (so *stateObject) Balance() *big.Int { - return so.account.Balance().BigInt() + balance := so.account.Balance().BigInt() + if balance == nil { + return zeroBalance + } + return balance } // CodeHash returns the state object's code hash. func (so *stateObject) CodeHash() []byte { + if so.account == nil || len(so.account.CodeHash) == 0 { + return emptyCodeHash + } return so.account.CodeHash } // Nonce returns the state object's current nonce (sequence number). func (so *stateObject) Nonce() uint64 { + if so.account == nil { + return 0 + } return so.account.Sequence } // Code returns the contract code associated with this object, if any. func (so *stateObject) Code(_ ethstate.Database) []byte { - if so.code != nil { + if len(so.code) > 0 { return so.code } @@ -286,10 +300,9 @@ func (so *stateObject) Code(_ ethstate.Database) []byte { code := store.Get(so.CodeHash()) if len(code) == 0 { - so.setError(fmt.Errorf("failed to get code hash %x for address: %x", so.CodeHash(), so.Address())) + so.setError(fmt.Errorf("failed to get code hash %x for address %s", so.CodeHash(), so.Address().String())) } - so.code = code return code } @@ -321,7 +334,7 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e // otherwise load the value from the KVStore ctx := so.stateDB.ctx - store := ctx.KVStore(so.stateDB.storageKey) + store := ctx.KVStore(so.stateDB.storeKey) rawValue := store.Get(prefixKey.Bytes()) if len(rawValue) > 0 { @@ -341,7 +354,7 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e func (so *stateObject) ReturnGas(gas *big.Int) {} func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject { - newStateObj := newObject(db, so.account) + newStateObj := newStateObject(db, so.account) newStateObj.code = so.code newStateObj.dirtyStorage = so.dirtyStorage.Copy() @@ -355,9 +368,11 @@ func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject { // empty returns whether the account is considered empty. func (so *stateObject) empty() bool { - return so.account.Sequence == 0 && - so.account.Balance().Sign() == 0 && - bytes.Equal(so.account.CodeHash, emptyCodeHash) + return so.account == nil || + (so.account != nil && + so.account.Sequence == 0 && + so.account.Balance().Sign() == 0 && + bytes.Equal(so.account.CodeHash, emptyCodeHash)) } // EncodeRLP implements rlp.Encoder. diff --git a/x/evm/types/state_transition.go b/x/evm/types/state_transition.go index 058d9d57..af0dece8 100644 --- a/x/evm/types/state_transition.go +++ b/x/evm/types/state_transition.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -27,14 +28,24 @@ type StateTransition struct { Simulate bool } +// ReturnData represents what's returned from a transition +type ReturnData struct { + Logs []*ethtypes.Log + Bloom *big.Int + Result *sdk.Result +} + +// TODO: move to keeper // TransitionCSDB performs an evm state transition from a transaction -func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) { +// TODO: update godoc, it doesn't explain what it does in depth. +func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) { + returnData := new(ReturnData) contractCreation := st.Recipient == nil cost, err := core.IntrinsicGas(st.Payload, contractCreation, true) if err != nil { - return nil, sdk.ErrOutOfGas("invalid intrinsic gas for transaction").Result() + return nil, fmt.Errorf("invalid intrinsic gas for transaction: %s", err.Error()) } // This gas limit the the transaction gas limit with intrinsic gas subtracted @@ -68,21 +79,20 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) CanTransfer: core.CanTransfer, Transfer: core.Transfer, Origin: st.Sender, - Coinbase: common.Address{}, + Coinbase: common.Address{}, // TODO: explain why this is empty BlockNumber: big.NewInt(ctx.BlockHeight()), Time: big.NewInt(ctx.BlockHeader().Time.Unix()), - Difficulty: big.NewInt(0x30000), // unused + Difficulty: big.NewInt(0), // unused. Only required in PoW context GasLimit: gasLimit, GasPrice: ctx.MinGasPrices().AmountOf(emint.DenomDefault).Int, } - vmenv := vm.NewEVM(context, csdb, GenerateChainConfig(st.ChainID), vm.Config{}) + evm := vm.NewEVM(context, csdb, GenerateChainConfig(st.ChainID), vm.Config{}) var ( ret []byte leftOverGas uint64 addr common.Address - vmerr error senderRef = vm.AccountRef(st.Sender) ) @@ -91,52 +101,77 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) // Set nonce of sender account before evm state transition for usage in generating Create address st.Csdb.SetNonce(st.Sender, st.AccountNonce) - if contractCreation { - ret, addr, leftOverGas, vmerr = vmenv.Create(senderRef, st.Payload, gasLimit, st.Amount) - } else { + switch contractCreation { + case true: + ret, addr, leftOverGas, err = evm.Create(senderRef, st.Payload, gasLimit, st.Amount) + default: // Increment the nonce for the next transaction (just for evm state transition) csdb.SetNonce(st.Sender, csdb.GetNonce(st.Sender)+1) - - ret, leftOverGas, vmerr = vmenv.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount) + ret, leftOverGas, err = evm.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount) } + if err != nil { + return nil, err + } + + gasConsumed := gasLimit - leftOverGas + // Resets nonce to value pre state transition st.Csdb.SetNonce(st.Sender, currentNonce) // Generate bloom filter to be saved in tx receipt data bloomInt := big.NewInt(0) var bloomFilter ethtypes.Bloom + var logs []*ethtypes.Log if st.THash != nil && !st.Simulate { - logs := csdb.GetLogs(*st.THash) + logs, err = csdb.GetLogs(*st.THash) + if err != nil { + return nil, err + } bloomInt = ethtypes.LogsBloom(logs) bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes()) } // Encode all necessary data into slice of bytes to return in sdk result - returnData := EncodeReturnData(addr, bloomFilter, ret) + res := &ResultData{ + Address: addr, + Bloom: bloomFilter, + Logs: logs, + Ret: ret, + } + + resultData, err := EncodeResultData(res) + if err != nil { + return nil, err + } // handle errors - if vmerr != nil { - res := emint.ErrVMExecution(vmerr.Error()).Result() - if vmerr == vm.ErrOutOfGas || vmerr == vm.ErrCodeStoreOutOfGas { - res = sdk.ErrOutOfGas("EVM execution went out of gas").Result() + if err != nil { + if err == vm.ErrOutOfGas || err == vm.ErrCodeStoreOutOfGas { + return nil, fmt.Errorf("evm execution went out of gas: %s", err.Error()) } - res.Data = returnData + // Consume gas before returning - ctx.GasMeter().ConsumeGas(gasLimit-leftOverGas, "EVM execution consumption") - return nil, res + ctx.GasMeter().ConsumeGas(gasConsumed, "EVM execution consumption") + return nil, err } // TODO: Refund unused gas here, if intended in future if !st.Simulate { // Finalise state if not a simulated transaction - st.Csdb.Finalise(true) // Change to depend on config + // TODO: change to depend on config + if err := st.Csdb.Finalise(true); err != nil { + return nil, err + } } // Consume gas from evm execution // Out of gas check does not need to be done here since it is done within the EVM execution - ctx.WithGasMeter(currentGasMeter).GasMeter().ConsumeGas(gasLimit-leftOverGas, "EVM execution consumption") + ctx.WithGasMeter(currentGasMeter).GasMeter().ConsumeGas(gasConsumed, "EVM execution consumption") - return bloomInt, sdk.Result{Data: returnData, GasUsed: st.GasLimit - leftOverGas} + returnData.Logs = logs + returnData.Bloom = bloomInt + returnData.Result = &sdk.Result{Data: resultData, GasUsed: gasConsumed} + return returnData, nil } diff --git a/x/evm/types/statedb.go b/x/evm/types/statedb.go index 763c83ef..576a625a 100644 --- a/x/evm/types/statedb.go +++ b/x/evm/types/statedb.go @@ -7,8 +7,6 @@ import ( "sync" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - emint "github.com/cosmos/ethermint/types" ethcmn "github.com/ethereum/go-ethereum/common" @@ -41,9 +39,9 @@ type CommitStateDB struct { // StateDB interface. Perhaps there is a better way. ctx sdk.Context - ak auth.AccountKeeper - storageKey sdk.StoreKey - codeKey sdk.StoreKey + codeKey sdk.StoreKey + storeKey sdk.StoreKey // i.e storage key + accountKeeper AccountKeeper // maps that hold 'live' objects, which will get modified while processing a // state transition @@ -84,12 +82,14 @@ type CommitStateDB struct { // // CONTRACT: Stores used for state must be cache-wrapped as the ordering of the // key/value space matters in determining the merkle root. -func NewCommitStateDB(ctx sdk.Context, ak auth.AccountKeeper, storageKey, codeKey sdk.StoreKey) *CommitStateDB { +func NewCommitStateDB( + ctx sdk.Context, codeKey, storeKey sdk.StoreKey, ak AccountKeeper, +) *CommitStateDB { return &CommitStateDB{ ctx: ctx, - ak: ak, - storageKey: storageKey, codeKey: codeKey, + storeKey: storeKey, + accountKeeper: ak, stateObjects: make(map[ethcmn.Address]*stateObject), stateObjectsDirty: make(map[ethcmn.Address]struct{}), logs: make(map[ethcmn.Hash][]*ethtypes.Log), @@ -288,12 +288,28 @@ func (csdb *CommitStateDB) GetCommittedState(addr ethcmn.Address, hash ethcmn.Ha } // GetLogs returns the current logs for a given hash in the state. -func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) []*ethtypes.Log { - return csdb.logs[hash] +func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) { + if csdb.logs[hash] != nil { + return csdb.logs[hash], nil + } + + store := csdb.ctx.KVStore(csdb.storeKey) + + encLogs := store.Get(LogsKey(hash[:])) + if len(encLogs) == 0 { + return []*ethtypes.Log{}, nil + } + + logs, err := DecodeLogs(encLogs) + if err != nil { + return []*ethtypes.Log{}, err + } + + return logs, nil } // Logs returns all the current logs in the state. -func (csdb *CommitStateDB) Logs() []*ethtypes.Log { +func (csdb *CommitStateDB) AllLogs() []*ethtypes.Log { // nolint: prealloc var logs []*ethtypes.Log for _, lgs := range csdb.logs { @@ -364,7 +380,9 @@ func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (ethcmn.Hash, error) } // update the object in the KVStore - csdb.updateStateObject(so) + if err := csdb.updateStateObject(so); err != nil { + return ethcmn.Hash{}, err + } } delete(csdb.stateObjectsDirty, addr) @@ -379,7 +397,7 @@ func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (ethcmn.Hash, error) // Finalise finalizes the state objects (accounts) state by setting their state, // removing the csdb destructed objects and clearing the journal as well as the // refunds. -func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) { +func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) error { for addr := range csdb.journal.dirties { so, exist := csdb.stateObjects[addr] if !exist { @@ -401,7 +419,9 @@ func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) { // Set all the dirty state storage items for the state object in the // KVStore and finally set the account in the account mapper. so.commitState() - csdb.updateStateObject(so) + if err := csdb.updateStateObject(so); err != nil { + return err + } } csdb.stateObjectsDirty[addr] = struct{}{} @@ -409,6 +429,7 @@ func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) { // invalidate journal because reverting across transactions is not allowed csdb.clearJournalAndRefund() + return nil } // IntermediateRoot returns the current root hash of the state. It is called in @@ -418,21 +439,24 @@ func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) { // NOTE: The SDK has not concept or method of getting any intermediate merkle // root as commitment of the merkle-ized tree doesn't happen until the // BaseApps' EndBlocker. -func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash { - csdb.Finalise(deleteEmptyObjects) +func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) (ethcmn.Hash, error) { + if err := csdb.Finalise(deleteEmptyObjects); err != nil { + return ethcmn.Hash{}, err + } - return ethcmn.Hash{} + return ethcmn.Hash{}, nil } // updateStateObject writes the given state object to the store. -func (csdb *CommitStateDB) updateStateObject(so *stateObject) { - csdb.ak.SetAccount(csdb.ctx, so.account) +func (csdb *CommitStateDB) updateStateObject(so *stateObject) error { + csdb.accountKeeper.SetAccount(csdb.ctx, so.account) + return nil } // deleteStateObject removes the given state object from the state store. func (csdb *CommitStateDB) deleteStateObject(so *stateObject) { so.deleted = true - csdb.ak.RemoveAccount(csdb.ctx, so.account) + csdb.accountKeeper.RemoveAccount(csdb.ctx, so.account) } // ---------------------------------------------------------------------------- @@ -543,10 +567,10 @@ func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error { // UpdateAccounts updates the nonce and coin balances of accounts func (csdb *CommitStateDB) UpdateAccounts() { for addr, so := range csdb.stateObjects { - currAcc := csdb.ak.GetAccount(csdb.ctx, sdk.AccAddress(addr.Bytes())) + currAcc := csdb.accountKeeper.GetAccount(csdb.ctx, sdk.AccAddress(addr.Bytes())) emintAcc, ok := currAcc.(*emint.Account) if ok { - if (so.Balance() != emintAcc.Balance().BigInt()) || (so.Nonce() != emintAcc.GetSequence()) { + if so.Balance() != emintAcc.Balance().BigInt() || so.Nonce() != emintAcc.GetSequence() { // If queried account's balance or nonce are invalid, update the account pointer so.account = emintAcc } @@ -602,9 +626,9 @@ func (csdb *CommitStateDB) Copy() *CommitStateDB { // copy all the basic fields, initialize the memory ones state := &CommitStateDB{ ctx: csdb.ctx, - ak: csdb.ak, - storageKey: csdb.storageKey, codeKey: csdb.codeKey, + storeKey: csdb.storeKey, + accountKeeper: csdb.accountKeeper, stateObjects: make(map[ethcmn.Address]*stateObject, len(csdb.journal.dirties)), stateObjectsDirty: make(map[ethcmn.Address]struct{}, len(csdb.journal.dirties)), refund: csdb.refund, @@ -663,8 +687,9 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu return nil } - store := csdb.ctx.KVStore(csdb.storageKey) + store := csdb.ctx.KVStore(csdb.storeKey) iter := sdk.KVStorePrefixIterator(store, so.Address().Bytes()) + defer iter.Close() for ; iter.Valid(); iter.Next() { key := ethcmn.BytesToHash(iter.Key()) @@ -678,7 +703,6 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu cb(key, ethcmn.BytesToHash(value)) } - iter.Close() return nil } @@ -698,8 +722,9 @@ func (csdb *CommitStateDB) GetOrNewStateObject(addr ethcmn.Address) StateObject func (csdb *CommitStateDB) createObject(addr ethcmn.Address) (newObj, prevObj *stateObject) { prevObj = csdb.getStateObject(addr) - acc := csdb.ak.NewAccountWithAddress(csdb.ctx, sdk.AccAddress(addr.Bytes())) - newObj = newObject(csdb, acc) + acc := csdb.accountKeeper.NewAccountWithAddress(csdb.ctx, sdk.AccAddress(addr.Bytes())) + + newObj = newStateObject(csdb, acc) newObj.setNonce(0) // sets the object to dirty if prevObj == nil { @@ -732,14 +757,14 @@ func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *sta } // otherwise, attempt to fetch the account from the account mapper - acc := csdb.ak.GetAccount(csdb.ctx, addr.Bytes()) + acc := csdb.accountKeeper.GetAccount(csdb.ctx, addr.Bytes()) if acc == nil { csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes())) return nil } // insert the state object into the live set - so := newObject(csdb, acc) + so := newStateObject(csdb, acc) csdb.setStateObject(so) return so @@ -748,3 +773,10 @@ func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *sta func (csdb *CommitStateDB) setStateObject(so *stateObject) { csdb.stateObjects[so.Address()] = so } + +// RawDump returns a raw state dump. +// +// TODO: Implement if we need it, especially for the RPC API. +func (csdb *CommitStateDB) RawDump() ethstate.Dump { + return ethstate.Dump{} +} diff --git a/x/evm/types/statedb_test.go b/x/evm/types/statedb_test.go index 9cf624bb..ae7af4d1 100644 --- a/x/evm/types/statedb_test.go +++ b/x/evm/types/statedb_test.go @@ -1,80 +1,40 @@ -package types +package types_test import ( "math/big" - "testing" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" - sdkstore "github.com/cosmos/cosmos-sdk/store/types" + "github.com/stretchr/testify/suite" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/params" - "github.com/stretchr/testify/require" ethcmn "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/cosmos/ethermint/types" + "github.com/cosmos/ethermint/app" + "github.com/cosmos/ethermint/x/evm/keeper" abci "github.com/tendermint/tendermint/abci/types" - tmlog "github.com/tendermint/tendermint/libs/log" - dbm "github.com/tendermint/tm-db" ) -func newTestCodec() *codec.Codec { - cdc := codec.New() +// nolint: unused +type StateDBTestSuite struct { + suite.Suite - RegisterCodec(cdc) - types.RegisterCodec(cdc) - auth.RegisterCodec(cdc) - sdk.RegisterCodec(cdc) - codec.RegisterCrypto(cdc) - - return cdc + ctx sdk.Context + querier sdk.Querier + app *app.EthermintApp } -func setupStateDB() (*CommitStateDB, error) { - accKey := sdk.NewKVStoreKey("acc") - storageKey := sdk.NewKVStoreKey(EvmStoreKey) - codeKey := sdk.NewKVStoreKey(EvmCodeKey) - logger := tmlog.NewNopLogger() +func (suite *StateDBTestSuite) SetupTest() { + checkTx := false - db := dbm.NewMemDB() - - // create logger, codec and root multi-store - cdc := newTestCodec() - cms := store.NewCommitMultiStore(db) - - // The ParamsKeeper handles parameter storage for the application - keyParams := sdk.NewKVStoreKey(params.StoreKey) - tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) - paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) - // Set specific supspaces - authSubspace := paramsKeeper.Subspace(auth.DefaultParamspace) - ak := auth.NewAccountKeeper(cdc, accKey, authSubspace, types.ProtoBaseAccount) - - // mount stores - keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey} - for _, key := range keys { - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, nil) - } - - cms.SetPruning(sdkstore.PruneNothing) - - // load latest version (root) - if err := cms.LoadLatestVersion(); err != nil { - return nil, err - } - - ms := cms.CacheMultiStore() - ctx := sdk.NewContext(ms, abci.Header{}, false, logger) - return NewCommitStateDB(ctx, ak, storageKey, codeKey), nil + suite.app = app.Setup(checkTx) + suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1}) + suite.querier = keeper.NewQuerier(suite.app.EvmKeeper) } -func TestBloomFilter(t *testing.T) { - stateDB, err := setupStateDB() - require.NoError(t, err) +func (suite *StateDBTestSuite) TestBloomFilter() { + stateDB := suite.app.EvmKeeper.CommitStateDB // Prepare db for logs tHash := ethcmn.BytesToHash([]byte{0x1}) @@ -87,14 +47,15 @@ func TestBloomFilter(t *testing.T) { stateDB.AddLog(&log) // Get log from db - logs := stateDB.GetLogs(tHash) - require.Equal(t, len(logs), 1) + logs, err := stateDB.GetLogs(tHash) + suite.Require().NoError(err) + suite.Require().Equal(len(logs), 1) // get logs bloom from the log bloomInt := ethtypes.LogsBloom(logs) bloomFilter := ethtypes.BytesToBloom(bloomInt.Bytes()) // Check to make sure bloom filter will succeed on - require.True(t, ethtypes.BloomLookup(bloomFilter, contractAddress)) - require.False(t, ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2)))) + suite.Require().True(ethtypes.BloomLookup(bloomFilter, contractAddress)) + suite.Require().False(ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2)))) } diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go index 706b2c0d..cb9c1d46 100644 --- a/x/evm/types/utils.go +++ b/x/evm/types/utils.go @@ -13,11 +13,6 @@ import ( "golang.org/x/crypto/sha3" ) -const ( - bloomIdx = ethcmn.AddressLength - returnIdx = bloomIdx + ethtypes.BloomByteLength -) - // GenerateEthAddress generates an Ethereum address. func GenerateEthAddress() ethcmn.Address { priv, err := crypto.GenerateKey() @@ -52,23 +47,41 @@ func rlpHash(x interface{}) (hash ethcmn.Hash) { return hash } +// ResultData represents the data returned in an sdk.Result +type ResultData struct { + Address ethcmn.Address + Bloom ethtypes.Bloom + Logs []*ethtypes.Log + Ret []byte +} + // EncodeReturnData takes all of the necessary data from the EVM execution -// and returns the data as a byte slice -func EncodeReturnData(addr ethcmn.Address, bloom ethtypes.Bloom, evmRet []byte) []byte { - // Append address, bloom, evm return bytes in that order - returnData := append(addr.Bytes(), bloom.Bytes()...) - return append(returnData, evmRet...) +// and returns the data as a byte slice encoded with amino +func EncodeResultData(data *ResultData) ([]byte, error) { + return ModuleCdc.MarshalBinaryLengthPrefixed(data) } -// DecodeReturnData decodes the byte slice of values to their respective types -func DecodeReturnData(bytes []byte) (addr ethcmn.Address, bloom ethtypes.Bloom, ret []byte, err error) { - if len(bytes) >= returnIdx { - addr = ethcmn.BytesToAddress(bytes[:bloomIdx]) - bloom = ethtypes.BytesToBloom(bytes[bloomIdx:returnIdx]) - ret = bytes[returnIdx:] - } else { - err = fmt.Errorf("invalid format for encoded data, message must be an EVM state transition") +// DecodeResultData decodes an amino-encoded byte slice into ReturnData +func DecodeResultData(in []byte) (ResultData, error) { + data := new(ResultData) + err := ModuleCdc.UnmarshalBinaryLengthPrefixed(in, data) + if err != nil { + return ResultData{}, err } - - return + return *data, nil +} + +// EncodeLogs encodes an array of logs using amino +func EncodeLogs(logs []*ethtypes.Log) ([]byte, error) { + return ModuleCdc.MarshalBinaryLengthPrefixed(logs) +} + +// DecodeLogs decodes an amino-encoded byte array into an array of logs +func DecodeLogs(in []byte) ([]*ethtypes.Log, error) { + logs := []*ethtypes.Log{} + err := ModuleCdc.UnmarshalBinaryLengthPrefixed(in, &logs) + if err != nil { + return nil, err + } + return logs, nil } diff --git a/x/evm/types/utils_test.go b/x/evm/types/utils_test.go index 9611963a..789d87a3 100644 --- a/x/evm/types/utils_test.go +++ b/x/evm/types/utils_test.go @@ -13,12 +13,23 @@ func TestEvmDataEncoding(t *testing.T) { bloom := ethtypes.BytesToBloom([]byte{0x1, 0x3}) ret := []byte{0x5, 0x8} - encoded := EncodeReturnData(addr, bloom, ret) - - decAddr, decBloom, decRet, err := DecodeReturnData(encoded) + data := &ResultData{ + Address: addr, + Bloom: bloom, + Logs: []*ethtypes.Log{{ + Data: []byte{1, 2, 3, 4}, + BlockNumber: 17, + }}, + Ret: ret, + } + enc, err := EncodeResultData(data) require.NoError(t, err) - require.Equal(t, addr, decAddr) - require.Equal(t, bloom, decBloom) - require.Equal(t, ret, decRet) + + res, err := DecodeResultData(enc) + require.NoError(t, err) + require.Equal(t, addr, res.Address) + require.Equal(t, bloom, res.Bloom) + require.Equal(t, data.Logs, res.Logs) + require.Equal(t, ret, res.Ret) }