R4R: CheckTx AnteHandler for Ethereum Tx Messages (#505)

* Rename Ethereum tx message
* Use new tx decoder in the ethermint app
* Update ante handler to prevent spam/dos
* Update ethereum msg signing/verification logic
* Implement secp256k1 key types
* Remove pointer from To method
* Move sig check to after inartistic gas check
* Add comment on chainID parsing
* Updated validateIntrinsicGas godoc
* Implement Fee method on eth tx msg
* Add reference to spec for recoverEthSig
* Upgrade TM to v0.27.0
This commit is contained in:
Alexander Bezobchuk
2018-12-18 11:10:04 -05:00
committed by GitHub
parent a3619584f8
commit b821a85a64
20 changed files with 871 additions and 327 deletions
+251 -45
View File
@@ -1,29 +1,28 @@
package app
import (
"encoding/hex"
"fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/ethermint/crypto"
"github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
var dummySecp256k1Pubkey secp256k1.PubKeySecp256k1 // used for tx simulation
ethcmn "github.com/ethereum/go-ethereum/common"
ethcore "github.com/ethereum/go-ethereum/core"
tmcrypto "github.com/tendermint/tendermint/crypto"
)
const (
memoCostPerByte = 1
maxMemoCharacters = 100
secp256k1VerifyCost = 100
memoCostPerByte sdk.Gas = 3
secp256k1VerifyCost = 21000
)
func init() {
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
copy(dummySecp256k1Pubkey[:], bz)
}
// NewAnteHandler returns an ante handelr responsible for attempting to route an
// NewAnteHandler returns an ante handler responsible for attempting to route an
// Ethereum or SDK transaction to an internal ante handler for performing
// transaction-level processing (e.g. fee payment, signature verification) before
// being passed onto it's respective handler.
@@ -35,22 +34,130 @@ func NewAnteHandler(ak auth.AccountKeeper, fck auth.FeeCollectionKeeper) sdk.Ant
ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, res sdk.Result, abort bool) {
stdTx, ok := tx.(auth.StdTx)
if !ok {
return ctx, sdk.ErrInternal("transaction type invalid: must be StdTx").Result(), true
switch castTx := tx.(type) {
case auth.StdTx:
return sdkAnteHandler(ctx, ak, fck, castTx, sim)
case *evmtypes.EthereumTxMsg:
return ethAnteHandler(ctx, castTx, ak)
default:
return ctx, sdk.ErrInternal(fmt.Sprintf("transaction type invalid: %T", tx)).Result(), true
}
}
}
// ----------------------------------------------------------------------------
// SDK Ante Handler
func sdkAnteHandler(
ctx sdk.Context, ak auth.AccountKeeper, fck auth.FeeCollectionKeeper, stdTx auth.StdTx, sim bool,
) (newCtx sdk.Context, res sdk.Result, abort bool) {
// Ensure that the provided fees meet a minimum threshold for the validator,
// if this is a CheckTx. This is only for local mempool purposes, and thus
// is only ran on check tx.
if ctx.IsCheckTx() && !sim {
res := auth.EnsureSufficientMempoolFees(ctx, stdTx)
if !res.IsOK() {
return newCtx, res, true
}
}
newCtx = auth.SetGasMeter(sim, ctx, stdTx)
// AnteHandlers must have their own defer/recover in order for the BaseApp
// to know how much gas was used! This is because the GasMeter is created in
// the AnteHandler, but if it panics the context won't be set properly in
// runTx's recover call.
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
case sdk.ErrorOutOfGas:
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
res = sdk.ErrOutOfGas(log).Result()
res.GasWanted = stdTx.Fee.Gas
res.GasUsed = newCtx.GasMeter().GasConsumed()
abort = true
default:
panic(r)
}
}
}()
if err := stdTx.ValidateBasic(); err != nil {
return newCtx, err.Result(), true
}
newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo")
signerAccs, res := auth.GetSignerAccs(newCtx, ak, stdTx.GetSigners())
if !res.IsOK() {
return newCtx, res, true
}
// the first signer pays the transaction fees
if !stdTx.Fee.Amount.IsZero() {
signerAccs[0], res = auth.DeductFees(signerAccs[0], stdTx.Fee)
if !res.IsOK() {
return newCtx, res, true
}
// TODO: Handle gas/fee checking and spam prevention. We may need two
// different models for SDK and Ethereum txs. The SDK currently supports a
// primitive model where a constant gas price is used.
//
// Ref: #473
fck.AddCollectedFees(newCtx, stdTx.Fee.Amount)
}
if ethTx, ok := isEthereumTx(stdTx); ethTx != nil && ok {
return ethAnteHandler(ctx, ethTx, ak)
isGenesis := ctx.BlockHeight() == 0
signBytesList := auth.GetSignBytesList(newCtx.ChainID(), stdTx, signerAccs, isGenesis)
stdSigs := stdTx.GetSignatures()
for i := 0; i < len(stdSigs); i++ {
// check signature, return account with incremented nonce
signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytesList[i], sim)
if !res.IsOK() {
return newCtx, res, true
}
return auth.NewAnteHandler(ak, fck)(ctx, stdTx, sim)
ak.SetAccount(newCtx, signerAccs[i])
}
return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false
}
// processSig verifies the signature and increments the nonce. If the account
// doesn't have a pubkey, set it.
func processSig(
ctx sdk.Context, acc auth.Account, sig auth.StdSignature, signBytes []byte, sim bool,
) (updatedAcc auth.Account, res sdk.Result) {
pubKey, res := auth.ProcessPubKey(acc, sig, sim)
if !res.IsOK() {
return nil, res
}
err := acc.SetPubKey(pubKey)
if err != nil {
return nil, sdk.ErrInternal("failed to set PubKey on signer account").Result()
}
consumeSigGas(ctx.GasMeter(), pubKey)
if !sim && !pubKey.VerifyBytes(signBytes, sig.Signature) {
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
}
err = acc.SetSequence(acc.GetSequence() + 1)
if err != nil {
return nil, sdk.ErrInternal("failed to set account nonce").Result()
}
return acc, res
}
func consumeSigGas(meter sdk.GasMeter, pubkey tmcrypto.PubKey) {
switch pubkey.(type) {
case crypto.PubKeySecp256k1:
meter.ConsumeGas(secp256k1VerifyCost, "ante verify: secp256k1")
default:
panic("Unrecognized signature type")
}
}
@@ -58,34 +165,133 @@ func NewAnteHandler(ak auth.AccountKeeper, fck auth.FeeCollectionKeeper) sdk.Ant
// Ethereum Ante Handler
// ethAnteHandler defines an internal ante handler for an Ethereum transaction
// ethTx that implements the sdk.Msg interface. The Ethereum transaction is a
// single message inside a auth.StdTx.
//
// For now we simply pass the transaction on as the EVM shares common business
// logic of an ante handler. Anything not handled by the EVM that should be
// prior to transaction processing, should be done here.
// ethTxMsg. During CheckTx, the transaction is passed through a series of
// pre-message execution validation checks such as signature and account
// verification in addition to minimum fees being checked. Otherwise, during
// DeliverTx, the transaction is simply passed to the EVM which will also
// perform the same series of checks. The distinction is made in CheckTx to
// prevent spam and DoS attacks.
func ethAnteHandler(
ctx sdk.Context, ethTx *evmtypes.MsgEthereumTx, ak auth.AccountKeeper,
ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg, ak auth.AccountKeeper,
) (newCtx sdk.Context, res sdk.Result, abort bool) {
if ctx.IsCheckTx() {
// Only perform pre-message (Ethereum transaction) execution validation
// during CheckTx. Otherwise, during DeliverTx the EVM will handle them.
if res := validateEthTxCheckTx(ctx, ak, ethTxMsg); !res.IsOK() {
return newCtx, res, true
}
}
return ctx, sdk.Result{}, false
}
// ----------------------------------------------------------------------------
// Auxiliary
func validateEthTxCheckTx(
ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg,
) sdk.Result {
// isEthereumTx returns a boolean if a given standard SDK transaction contains
// an Ethereum transaction. If so, the transaction is also returned. A standard
// SDK transaction contains an Ethereum transaction if it only has a single
// message and that embedded message if of type MsgEthereumTx.
func isEthereumTx(tx auth.StdTx) (*evmtypes.MsgEthereumTx, bool) {
msgs := tx.GetMsgs()
if len(msgs) == 1 {
ethTx, ok := msgs[0].(*evmtypes.MsgEthereumTx)
if ok {
return ethTx, true
}
// parse the chainID from a string to a base-10 integer
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok {
return types.ErrInvalidChainID(fmt.Sprintf("invalid chainID: %s", ctx.ChainID())).Result()
}
return nil, false
// Validate sufficient fees have been provided that meet a minimum threshold
// defined by the proposer (for mempool purposes during CheckTx).
if res := ensureSufficientMempoolFees(ctx, ethTxMsg); !res.IsOK() {
return res
}
// validate enough intrinsic gas
if res := validateIntrinsicGas(ethTxMsg); !res.IsOK() {
return res
}
// validate sender/signature
signer, err := ethTxMsg.VerifySig(chainID)
if err != nil {
return sdk.ErrUnauthorized("signature verification failed").Result()
}
// validate account (nonce and balance checks)
if res := validateAccount(ctx, ak, ethTxMsg, signer); !res.IsOK() {
return res
}
return sdk.Result{}
}
// validateIntrinsicGas validates that the Ethereum tx message has enough to
// cover intrinsic gas. Intrinsic gas for a transaction is the amount of gas
// 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) sdk.Result {
gas, err := ethcore.IntrinsicGas(ethTxMsg.Data.Payload, ethTxMsg.To() == nil, false)
if err != nil {
return sdk.ErrInternal(fmt.Sprintf("failed to compute intrinsic gas cost: %s", err)).Result()
}
if ethTxMsg.Data.GasLimit < gas {
return sdk.ErrInternal(
fmt.Sprintf("intrinsic gas too low; %d < %d", ethTxMsg.Data.GasLimit, gas),
).Result()
}
return sdk.Result{}
}
// 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 ethcmn.Address,
) sdk.Result {
acc := ak.GetAccount(ctx, sdk.AccAddress(signer.Bytes()))
// on InitChain make sure account number == 0
if ctx.BlockHeight() == 0 && acc.GetAccountNumber() != 0 {
return sdk.ErrInternal(
fmt.Sprintf(
"invalid account number for height zero; got %d, expected 0", acc.GetAccountNumber(),
)).Result()
}
// Validate the transaction nonce is valid (equivalent to the sender accounts
// current nonce).
seq := acc.GetSequence()
if ethTxMsg.Data.AccountNonce != seq {
return sdk.ErrInvalidSequence(
fmt.Sprintf("nonce too low; got %d, expected %d", ethTxMsg.Data.AccountNonce, seq)).Result()
}
// validate sender has enough funds
balance := acc.GetCoins().AmountOf(types.DenomDefault)
if balance.BigInt().Cmp(ethTxMsg.Cost()) < 0 {
return sdk.ErrInsufficientFunds(
fmt.Sprintf("insufficient funds: %s < %s", balance, ethTxMsg.Cost()),
).Result()
}
return sdk.Result{}
}
// ensureSufficientMempoolFees verifies that enough fees have been provided by the
// Ethereum transaction that meet the minimum threshold set by the block
// proposer.
//
// NOTE: This should only be ran during a CheckTx mode.
func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg) sdk.Result {
// fee = GP * GL
fee := sdk.Coins{sdk.NewInt64Coin(types.DenomDefault, ethTxMsg.Fee().Int64())}
// it is assumed that the minimum fees will only include the single valid denom
if !ctx.MinimumFees().IsZero() && !fee.IsAllGTE(ctx.MinimumFees()) {
// reject the transaction that does not meet the minimum fee
return sdk.ErrInsufficientFee(
fmt.Sprintf("insufficient fee, got: %q required: %q", fee, ctx.MinimumFees()),
).Result()
}
return sdk.Result{}
}
+200 -63
View File
@@ -5,12 +5,14 @@ import (
"math/big"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
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(
@@ -45,64 +47,80 @@ func requireInvalidTx(
}
}
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)
acc1.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc1)
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
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) {
setup := newTestSetup()
setup.ctx = setup.ctx.WithBlockHeight(1)
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
acc1.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc1)
input.accKeeper.SetAccount(input.ctx, acc1)
acc2 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr2)
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
acc2.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc2)
input.accKeeper.SetAccount(input.ctx, acc2)
// require a valid SDK tx to pass
fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2)
msgs := []sdk.Msg{msg1}
privKeys := []crypto.PrivKey{priv1, priv2}
accNums := []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []int64{acc1.GetSequence(), acc2.GetSequence()}
privKeys := []tmcrypto.PrivKey{priv1, priv2}
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireValidTx(t, setup.anteHandler, setup.ctx, tx, false)
tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireValidTx(t, input.anteHandler, input.ctx, tx, false)
// require accounts to update
acc1 = setup.accKeeper.GetAccount(setup.ctx, addr1)
acc2 = setup.accKeeper.GetAccount(setup.ctx, addr2)
acc1 = input.accKeeper.GetAccount(input.ctx, addr1)
acc2 = input.accKeeper.GetAccount(input.ctx, addr2)
require.Equal(t, accSeqs[0]+1, acc1.GetSequence())
require.Equal(t, accSeqs[1]+1, acc2.GetSequence())
// 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, 20000, gas, []byte("test"))
tx = newTestEthTx(setup.ctx, ethMsg, priv1)
requireValidTx(t, setup.anteHandler, setup.ctx, tx, false)
}
func TestSDKInvalidSigs(t *testing.T) {
setup := newTestSetup()
setup.ctx = setup.ctx.WithBlockHeight(1)
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey()
addr3, priv3 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
acc1.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc1)
input.accKeeper.SetAccount(input.ctx, acc1)
acc2 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr2)
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
acc2.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc2)
input.accKeeper.SetAccount(input.ctx, acc2)
fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2)
@@ -110,66 +128,185 @@ func TestSDKInvalidSigs(t *testing.T) {
// require validation failure with no signers
msgs := []sdk.Msg{msg1}
privKeys := []crypto.PrivKey{}
accNums := []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []int64{acc1.GetSequence(), acc2.GetSequence()}
privKeys := []tmcrypto.PrivKey{}
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnauthorized)
tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
// require validation failure with invalid number of signers
msgs = []sdk.Msg{msg1}
privKeys = []crypto.PrivKey{priv1}
accNums = []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs = []int64{acc1.GetSequence(), acc2.GetSequence()}
privKeys = []tmcrypto.PrivKey{priv1}
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnauthorized)
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 = []crypto.PrivKey{priv1, priv2, priv3}
accNums = []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
accSeqs = []int64{acc1.GetSequence(), acc2.GetSequence(), 0}
privKeys = []tmcrypto.PrivKey{priv1, priv2, priv3}
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence(), 0}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnknownAddress)
tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnknownAddress)
}
func TestSDKInvalidAcc(t *testing.T) {
setup := newTestSetup()
setup.ctx = setup.ctx.WithBlockHeight(1)
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
acc1.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc1)
input.accKeeper.SetAccount(input.ctx, acc1)
fee := newTestStdFee()
msg1 := newTestMsg(addr1)
msgs := []sdk.Msg{msg1}
privKeys := []crypto.PrivKey{priv1}
privKeys := []tmcrypto.PrivKey{priv1}
// require validation failure with invalid account number
accNums := []int64{1}
accSeqs := []int64{acc1.GetSequence()}
accNums := []uint64{1}
accSeqs := []uint64{acc1.GetSequence()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence)
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 = []int64{acc1.GetAccountNumber()}
accSeqs = []int64{1}
accNums = []uint64{acc1.GetAccountNumber()}
accSeqs = []uint64{1}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence)
tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
}
func TestSDKGasConsumption(t *testing.T) {
// TODO: Test gas consumption and OOG once ante handler implementation stabilizes
t.SkipNow()
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)
acc.SetCoins(newTestCoins())
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)
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.WithMinimumFees(sdk.Coins{sdk.NewInt64Coin(types.DenomDefault, 500000)})
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
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)
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)
}
+5 -3
View File
@@ -11,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/stake"
"github.com/cosmos/ethermint/crypto"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
"github.com/pkg/errors"
@@ -74,7 +75,7 @@ type (
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.BaseApp)) *EthermintApp {
cdc := CreateCodec()
baseApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOpts...)
baseApp := bam.NewBaseApp(appName, logger, db, evmtypes.TxDecoder(cdc), baseAppOpts...)
app := &EthermintApp{
BaseApp: baseApp,
cdc: cdc,
@@ -89,8 +90,8 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.Ba
tParamsKey: storeKeyTransParams,
}
app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.accountKey, auth.ProtoBaseAccount)
app.paramsKeeper = params.NewKeeper(app.cdc, app.paramsKey, app.tParamsKey)
app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.accountKey, auth.ProtoBaseAccount)
app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.feeCollKey)
// register message handlers
@@ -106,7 +107,7 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.Ba
app.SetEndBlocker(app.EndBlocker)
app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.feeCollKeeper))
app.MountStoresIAVL(
app.MountStores(
app.mainKey, app.accountKey, app.stakeKey, app.slashingKey,
app.govKey, app.feeCollKey, app.paramsKey, app.storageKey,
)
@@ -165,6 +166,7 @@ func CreateCodec() *codec.Codec {
// TODO: Add remaining codec registrations:
// bank, staking, distribution, slashing, and gov
crypto.RegisterCodec(cdc)
evmtypes.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
-20
View File
@@ -1,20 +0,0 @@
package app
import (
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
dbm "github.com/tendermint/tendermint/libs/db"
)
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) {
db := dbm.NewMemDB()
capKey := sdk.NewKVStoreKey("capkey")
capKey2 := sdk.NewKVStoreKey("capkey2")
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db)
ms.LoadLatestVersion()
return ms, capKey, capKey2
}
+40 -25
View File
@@ -6,46 +6,61 @@ import (
"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/ethermint/crypto"
"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"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
)
var testDenom = "testcoin"
type testSetup struct {
ctx sdk.Context
cdc *codec.Codec
accKeeper auth.AccountKeeper
feeKeeper auth.FeeCollectionKeeper
anteHandler sdk.AnteHandler
}
func newTestSetup() testSetup {
cdc := codec.New()
ms, capKey, capKey2 := setupMultiStore()
db := dbm.NewMemDB()
authCapKey := sdk.NewKVStoreKey("authCapKey")
feeCapKey := sdk.NewKVStoreKey("feeCapKey")
keyParams := sdk.NewKVStoreKey("params")
tkeyParams := sdk.NewTransientStoreKey("transient_params")
auth.RegisterBaseAccount(cdc)
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(feeCapKey, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeIAVL, db)
ms.LoadLatestVersion()
accKeeper := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
feeKeeper := auth.NewFeeCollectionKeeper(cdc, capKey2)
cdc := CreateCodec()
cdc.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
accKeeper := auth.NewAccountKeeper(cdc, authCapKey, auth.ProtoBaseAccount)
feeKeeper := auth.NewFeeCollectionKeeper(cdc, feeCapKey)
anteHandler := NewAnteHandler(accKeeper, feeKeeper)
ctx := sdk.NewContext(
ms,
abci.Header{ChainID: "3", Time: time.Now().UTC()},
false,
true,
log.NewNopLogger(),
)
return testSetup{
ctx: ctx,
cdc: cdc,
accKeeper: accKeeper,
feeKeeper: feeKeeper,
anteHandler: anteHandler,
@@ -57,55 +72,55 @@ func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
}
func newTestCoins() sdk.Coins {
return sdk.Coins{sdk.NewInt64Coin(testDenom, 10000000)}
return sdk.Coins{sdk.NewInt64Coin(types.DenomDefault, 500000000)}
}
func newTestStdFee() auth.StdFee {
return auth.NewStdFee(5000, sdk.NewInt64Coin(testDenom, 150))
return auth.NewStdFee(220000, sdk.NewInt64Coin(types.DenomDefault, 150))
}
// GenerateAddress generates an Ethereum address.
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
priv := tmsecp256k1.GenPrivKey()
addr := sdk.AccAddress(priv.PubKey().Address())
return addr, priv
privkey, _ := crypto.GenerateKey()
addr := ethcrypto.PubkeyToAddress(privkey.PublicKey)
return sdk.AccAddress(addr.Bytes()), privkey
}
func newTestSDKTx(
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey,
accNums []int64, seqs []int64, fee auth.StdFee,
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,
AccountNumber: accNums[i],
Sequence: seqs[i],
PubKey: priv.PubKey(),
Signature: sig,
}
}
return auth.NewStdTx(msgs, fee, sigs, "")
}
func newTestEthTx(ctx sdk.Context, msg *evmtypes.MsgEthereumTx, priv tmcrypto.PrivKey) sdk.Tx {
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, err := crypto.PrivKeyToSecp256k1(priv)
if err != nil {
panic(fmt.Sprintf("failed to convert private key: %s", err))
privkey, ok := priv.(crypto.PrivKeySecp256k1)
if !ok {
panic(fmt.Sprintf("invalid private key type: %T", priv))
}
msg.Sign(chainID, privKey)
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "")
msg.Sign(chainID, privkey.ToECDSA())
return msg
}