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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 871 additions and 327 deletions

26
Gopkg.lock generated
View File

@ -33,7 +33,8 @@
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
[[projects]] [[projects]]
digest = "1:e70ff0ca07fdc5dfe6cf280917ae4434f069f164a0bf18681e37cb19b01ee151" branch = "develop"
digest = "1:83eccb94de6a2aadff1bddb7020e3850131645ff45e9ce9093d1604e407130e7"
name = "github.com/cosmos/cosmos-sdk" name = "github.com/cosmos/cosmos-sdk"
packages = [ packages = [
"baseapp", "baseapp",
@ -56,7 +57,7 @@
"x/stake/types", "x/stake/types",
] ]
pruneopts = "T" pruneopts = "T"
revision = "1ea0e4c457fc105b48131a60e3d28c6c1bb32cc0" revision = "ec9c4ea543b5d0f558cf6ad9f1386d26cfe87f28"
[[projects]] [[projects]]
digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a" digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a"
@ -530,23 +531,23 @@
revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df"
[[projects]] [[projects]]
digest = "1:bcaa26e82b7707fbdfa6d0506ff1f30158eeb23126f761ac65881ed51339bf9e" digest = "1:02462ad4cc9b135c4ebfb9edccb53e8c705bbebdbf8664799ea641440188387e"
name = "github.com/tendermint/go-amino" name = "github.com/tendermint/go-amino"
packages = ["."] packages = ["."]
pruneopts = "T" pruneopts = "T"
revision = "6dcc6ddc143e116455c94b25c1004c99e0d0ca12" revision = "dc14acf9ef15f85828bfbc561ed9dd9d2a284885"
version = "v0.14.0" version = "v0.14.1"
[[projects]] [[projects]]
digest = "1:1e4b8f8f8c428af22d0cc68b1478bef4e144edefc4bb967d4d09aaddbc8cd71e" digest = "1:8a1dc8fc625c867614b48327112718c51c0ca83453c8a043f2f23721e19b353f"
name = "github.com/tendermint/iavl" name = "github.com/tendermint/iavl"
packages = ["."] packages = ["."]
pruneopts = "T" pruneopts = "T"
revision = "fa74114f764f9827c4ad5573f990ed25bf8c4bac" revision = "de0740903a67b624d887f9055d4c60175dcfa758"
version = "v0.11.1" version = "v0.12.0"
[[projects]] [[projects]]
digest = "1:ea9b485e28ee25ef860451187afdec9c38630932e1f999114eb7cab7f8c89966" digest = "1:844c7ba6332e1e6f073d7ac69768fd19f761bdf5964b559bd4b47103a0629144"
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
packages = [ packages = [
"abci/client", "abci/client",
@ -609,8 +610,7 @@
"version", "version",
] ]
pruneopts = "T" pruneopts = "T"
revision = "22dcc92232cd04ce7381043e09d85dd536ae3b96" revision = "v0.27.0"
version = "v0.26.3"
[[projects]] [[projects]]
digest = "1:d738326441b0b732070d727891855573dcb579e74d82fcf9a9459d3257f2eb0c" digest = "1:d738326441b0b732070d727891855573dcb579e74d82fcf9a9459d3257f2eb0c"
@ -784,6 +784,7 @@
"github.com/ethereum/go-ethereum/core/types", "github.com/ethereum/go-ethereum/core/types",
"github.com/ethereum/go-ethereum/core/vm", "github.com/ethereum/go-ethereum/core/vm",
"github.com/ethereum/go-ethereum/crypto", "github.com/ethereum/go-ethereum/crypto",
"github.com/ethereum/go-ethereum/crypto/secp256k1",
"github.com/ethereum/go-ethereum/crypto/sha3", "github.com/ethereum/go-ethereum/crypto/sha3",
"github.com/ethereum/go-ethereum/params", "github.com/ethereum/go-ethereum/params",
"github.com/ethereum/go-ethereum/rlp", "github.com/ethereum/go-ethereum/rlp",
@ -792,11 +793,8 @@
"github.com/pkg/errors", "github.com/pkg/errors",
"github.com/stretchr/testify/require", "github.com/stretchr/testify/require",
"github.com/stretchr/testify/suite", "github.com/stretchr/testify/suite",
"github.com/tendermint/btcd/btcec",
"github.com/tendermint/tendermint/abci/types", "github.com/tendermint/tendermint/abci/types",
"github.com/tendermint/tendermint/crypto", "github.com/tendermint/tendermint/crypto",
"github.com/tendermint/tendermint/crypto/ed25519",
"github.com/tendermint/tendermint/crypto/secp256k1",
"github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/common",
"github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/db",
"github.com/tendermint/tendermint/libs/log", "github.com/tendermint/tendermint/libs/log",

View File

@ -7,8 +7,8 @@
[[constraint]] [[constraint]]
name = "github.com/cosmos/cosmos-sdk" name = "github.com/cosmos/cosmos-sdk"
# TODO: Replace this with 0.27 revision = "ec9c4ea543b5d0f558cf6ad9f1386d26cfe87f28"
revision = "1ea0e4c457fc105b48131a60e3d28c6c1bb32cc0" # version = "v0.28.0"
[[constraint]] [[constraint]]
name = "github.com/hashicorp/golang-lru" name = "github.com/hashicorp/golang-lru"
@ -40,20 +40,20 @@
[[override]] [[override]]
name = "github.com/tendermint/go-amino" name = "github.com/tendermint/go-amino"
version = "v0.14.0" version = "v0.14.1"
[[override]]
name = "github.com/tendermint/iavl"
version = "=v0.11.1"
[[override]] [[override]]
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
source = "https://github.com/tendermint/crypto" source = "https://github.com/tendermint/crypto"
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
[[override]]
name = "github.com/tendermint/iavl"
version = "v0.12.0"
[[override]] [[override]]
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
version = "v0.26.1" revision = "v0.27.0"
[[override]] [[override]]
name = "golang.org/x/sys" name = "golang.org/x/sys"

View File

@ -1,29 +1,28 @@
package app package app
import ( import (
"encoding/hex" "fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "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" 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 ( const (
memoCostPerByte = 1 memoCostPerByte sdk.Gas = 3
maxMemoCharacters = 100 secp256k1VerifyCost = 21000
secp256k1VerifyCost = 100
) )
func init() { // NewAnteHandler returns an ante handler responsible for attempting to route an
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
copy(dummySecp256k1Pubkey[:], bz)
}
// NewAnteHandler returns an ante handelr responsible for attempting to route an
// Ethereum or SDK transaction to an internal ante handler for performing // Ethereum or SDK transaction to an internal ante handler for performing
// transaction-level processing (e.g. fee payment, signature verification) before // transaction-level processing (e.g. fee payment, signature verification) before
// being passed onto it's respective handler. // 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, ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, res sdk.Result, abort bool) { ) (newCtx sdk.Context, res sdk.Result, abort bool) {
stdTx, ok := tx.(auth.StdTx) switch castTx := tx.(type) {
if !ok { case auth.StdTx:
return ctx, sdk.ErrInternal("transaction type invalid: must be StdTx").Result(), true 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
}
} }
// TODO: Handle gas/fee checking and spam prevention. We may need two newCtx = auth.SetGasMeter(sim, ctx, stdTx)
// different models for SDK and Ethereum txs. The SDK currently supports a
// primitive model where a constant gas price is used.
//
// Ref: #473
if ethTx, ok := isEthereumTx(stdTx); ethTx != nil && ok { // AnteHandlers must have their own defer/recover in order for the BaseApp
return ethAnteHandler(ctx, ethTx, ak) // 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
} }
return auth.NewAnteHandler(ak, fck)(ctx, stdTx, sim) 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
}
fck.AddCollectedFees(newCtx, stdTx.Fee.Amount)
}
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
}
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 // Ethereum Ante Handler
// ethAnteHandler defines an internal ante handler for an Ethereum transaction // ethAnteHandler defines an internal ante handler for an Ethereum transaction
// ethTx that implements the sdk.Msg interface. The Ethereum transaction is a // ethTxMsg. During CheckTx, the transaction is passed through a series of
// single message inside a auth.StdTx. // pre-message execution validation checks such as signature and account
// // verification in addition to minimum fees being checked. Otherwise, during
// For now we simply pass the transaction on as the EVM shares common business // DeliverTx, the transaction is simply passed to the EVM which will also
// logic of an ante handler. Anything not handled by the EVM that should be // perform the same series of checks. The distinction is made in CheckTx to
// prior to transaction processing, should be done here. // prevent spam and DoS attacks.
func ethAnteHandler( 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) { ) (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 return ctx, sdk.Result{}, false
} }
// ---------------------------------------------------------------------------- func validateEthTxCheckTx(
// Auxiliary ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg,
) sdk.Result {
// isEthereumTx returns a boolean if a given standard SDK transaction contains // parse the chainID from a string to a base-10 integer
// an Ethereum transaction. If so, the transaction is also returned. A standard chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
// SDK transaction contains an Ethereum transaction if it only has a single if !ok {
// message and that embedded message if of type MsgEthereumTx. return types.ErrInvalidChainID(fmt.Sprintf("invalid chainID: %s", ctx.ChainID())).Result()
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
}
} }
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{}
} }

View File

@ -5,12 +5,14 @@ import (
"math/big" "math/big"
"testing" "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" ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "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( 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) { func TestValidTx(t *testing.T) {
setup := newTestSetup() input := newTestSetup()
setup.ctx = setup.ctx.WithBlockHeight(1) input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey() addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey() addr2, priv2 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1) acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
acc1.SetCoins(newTestCoins()) 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()) acc2.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc2) input.accKeeper.SetAccount(input.ctx, acc2)
// require a valid SDK tx to pass // require a valid SDK tx to pass
fee := newTestStdFee() fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2) msg1 := newTestMsg(addr1, addr2)
msgs := []sdk.Msg{msg1} msgs := []sdk.Msg{msg1}
privKeys := []crypto.PrivKey{priv1, priv2} privKeys := []tmcrypto.PrivKey{priv1, priv2}
accNums := []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []int64{acc1.GetSequence(), acc2.GetSequence()} accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee) tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireValidTx(t, setup.anteHandler, setup.ctx, tx, false) requireValidTx(t, input.anteHandler, input.ctx, tx, false)
// require accounts to update // require accounts to update
acc1 = setup.accKeeper.GetAccount(setup.ctx, addr1) acc1 = input.accKeeper.GetAccount(input.ctx, addr1)
acc2 = setup.accKeeper.GetAccount(setup.ctx, addr2) acc2 = input.accKeeper.GetAccount(input.ctx, addr2)
require.Equal(t, accSeqs[0]+1, acc1.GetSequence()) require.Equal(t, accSeqs[0]+1, acc1.GetSequence())
require.Equal(t, accSeqs[1]+1, acc2.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) { func TestSDKInvalidSigs(t *testing.T) {
setup := newTestSetup() input := newTestSetup()
setup.ctx = setup.ctx.WithBlockHeight(1) input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey() addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey() addr2, priv2 := newTestAddrKey()
addr3, priv3 := newTestAddrKey() addr3, priv3 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1) acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
acc1.SetCoins(newTestCoins()) 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()) acc2.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc2) input.accKeeper.SetAccount(input.ctx, acc2)
fee := newTestStdFee() fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2) msg1 := newTestMsg(addr1, addr2)
@ -110,66 +128,185 @@ func TestSDKInvalidSigs(t *testing.T) {
// require validation failure with no signers // require validation failure with no signers
msgs := []sdk.Msg{msg1} msgs := []sdk.Msg{msg1}
privKeys := []crypto.PrivKey{} privKeys := []tmcrypto.PrivKey{}
accNums := []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []int64{acc1.GetSequence(), acc2.GetSequence()} accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee) tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnauthorized) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
// require validation failure with invalid number of signers // require validation failure with invalid number of signers
msgs = []sdk.Msg{msg1} msgs = []sdk.Msg{msg1}
privKeys = []crypto.PrivKey{priv1} privKeys = []tmcrypto.PrivKey{priv1}
accNums = []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()} accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs = []int64{acc1.GetSequence(), acc2.GetSequence()} accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee) tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnauthorized) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
// require validation failure with an invalid signer // require validation failure with an invalid signer
msg2 := newTestMsg(addr1, addr3) msg2 := newTestMsg(addr1, addr3)
msgs = []sdk.Msg{msg1, msg2} msgs = []sdk.Msg{msg1, msg2}
privKeys = []crypto.PrivKey{priv1, priv2, priv3} privKeys = []tmcrypto.PrivKey{priv1, priv2, priv3}
accNums = []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0} accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
accSeqs = []int64{acc1.GetSequence(), acc2.GetSequence(), 0} accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence(), 0}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee) tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnknownAddress) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnknownAddress)
} }
func TestSDKInvalidAcc(t *testing.T) { func TestSDKInvalidAcc(t *testing.T) {
setup := newTestSetup() input := newTestSetup()
setup.ctx = setup.ctx.WithBlockHeight(1) input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey() addr1, priv1 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1) acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
acc1.SetCoins(newTestCoins()) acc1.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc1) input.accKeeper.SetAccount(input.ctx, acc1)
fee := newTestStdFee() fee := newTestStdFee()
msg1 := newTestMsg(addr1) msg1 := newTestMsg(addr1)
msgs := []sdk.Msg{msg1} msgs := []sdk.Msg{msg1}
privKeys := []crypto.PrivKey{priv1} privKeys := []tmcrypto.PrivKey{priv1}
// require validation failure with invalid account number // require validation failure with invalid account number
accNums := []int64{1} accNums := []uint64{1}
accSeqs := []int64{acc1.GetSequence()} accSeqs := []uint64{acc1.GetSequence()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee) tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
// require validation failure with invalid sequence (nonce) // require validation failure with invalid sequence (nonce)
accNums = []int64{acc1.GetAccountNumber()} accNums = []uint64{acc1.GetAccountNumber()}
accSeqs = []int64{1} accSeqs = []uint64{1}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee) tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
} }
func TestSDKGasConsumption(t *testing.T) { func TestEthInvalidSig(t *testing.T) {
// TODO: Test gas consumption and OOG once ante handler implementation stabilizes input := newTestSetup()
t.SkipNow() 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)
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake"
"github.com/cosmos/ethermint/crypto"
evmtypes "github.com/cosmos/ethermint/x/evm/types" evmtypes "github.com/cosmos/ethermint/x/evm/types"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -74,7 +75,7 @@ type (
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.BaseApp)) *EthermintApp { func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.BaseApp)) *EthermintApp {
cdc := CreateCodec() cdc := CreateCodec()
baseApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOpts...) baseApp := bam.NewBaseApp(appName, logger, db, evmtypes.TxDecoder(cdc), baseAppOpts...)
app := &EthermintApp{ app := &EthermintApp{
BaseApp: baseApp, BaseApp: baseApp,
cdc: cdc, cdc: cdc,
@ -89,8 +90,8 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.Ba
tParamsKey: storeKeyTransParams, tParamsKey: storeKeyTransParams,
} }
app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.accountKey, auth.ProtoBaseAccount)
app.paramsKeeper = params.NewKeeper(app.cdc, app.paramsKey, app.tParamsKey) 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) app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.feeCollKey)
// register message handlers // register message handlers
@ -106,7 +107,7 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.Ba
app.SetEndBlocker(app.EndBlocker) app.SetEndBlocker(app.EndBlocker)
app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.feeCollKeeper)) app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.feeCollKeeper))
app.MountStoresIAVL( app.MountStores(
app.mainKey, app.accountKey, app.stakeKey, app.slashingKey, app.mainKey, app.accountKey, app.stakeKey, app.slashingKey,
app.govKey, app.feeCollKey, app.paramsKey, app.storageKey, app.govKey, app.feeCollKey, app.paramsKey, app.storageKey,
) )
@ -165,6 +166,7 @@ func CreateCodec() *codec.Codec {
// TODO: Add remaining codec registrations: // TODO: Add remaining codec registrations:
// bank, staking, distribution, slashing, and gov // bank, staking, distribution, slashing, and gov
crypto.RegisterCodec(cdc)
evmtypes.RegisterCodec(cdc) evmtypes.RegisterCodec(cdc)
auth.RegisterCodec(cdc) auth.RegisterCodec(cdc)
sdk.RegisterCodec(cdc) sdk.RegisterCodec(cdc)

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
}

View File

@ -6,46 +6,61 @@ import (
"time" "time"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/crypto" "github.com/cosmos/ethermint/crypto"
"github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types" evmtypes "github.com/cosmos/ethermint/x/evm/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmcrypto "github.com/tendermint/tendermint/crypto" 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" "github.com/tendermint/tendermint/libs/log"
) )
var testDenom = "testcoin"
type testSetup struct { type testSetup struct {
ctx sdk.Context ctx sdk.Context
cdc *codec.Codec
accKeeper auth.AccountKeeper accKeeper auth.AccountKeeper
feeKeeper auth.FeeCollectionKeeper feeKeeper auth.FeeCollectionKeeper
anteHandler sdk.AnteHandler anteHandler sdk.AnteHandler
} }
func newTestSetup() testSetup { func newTestSetup() testSetup {
cdc := codec.New() db := dbm.NewMemDB()
ms, capKey, capKey2 := setupMultiStore() 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) cdc := CreateCodec()
feeKeeper := auth.NewFeeCollectionKeeper(cdc, capKey2) cdc.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
accKeeper := auth.NewAccountKeeper(cdc, authCapKey, auth.ProtoBaseAccount)
feeKeeper := auth.NewFeeCollectionKeeper(cdc, feeCapKey)
anteHandler := NewAnteHandler(accKeeper, feeKeeper) anteHandler := NewAnteHandler(accKeeper, feeKeeper)
ctx := sdk.NewContext( ctx := sdk.NewContext(
ms, ms,
abci.Header{ChainID: "3", Time: time.Now().UTC()}, abci.Header{ChainID: "3", Time: time.Now().UTC()},
false, true,
log.NewNopLogger(), log.NewNopLogger(),
) )
return testSetup{ return testSetup{
ctx: ctx, ctx: ctx,
cdc: cdc,
accKeeper: accKeeper, accKeeper: accKeeper,
feeKeeper: feeKeeper, feeKeeper: feeKeeper,
anteHandler: anteHandler, anteHandler: anteHandler,
@ -57,28 +72,30 @@ func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
} }
func newTestCoins() sdk.Coins { func newTestCoins() sdk.Coins {
return sdk.Coins{sdk.NewInt64Coin(testDenom, 10000000)} return sdk.Coins{sdk.NewInt64Coin(types.DenomDefault, 500000000)}
} }
func newTestStdFee() auth.StdFee { 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. // GenerateAddress generates an Ethereum address.
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) { func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
priv := tmsecp256k1.GenPrivKey() privkey, _ := crypto.GenerateKey()
addr := sdk.AccAddress(priv.PubKey().Address()) addr := ethcrypto.PubkeyToAddress(privkey.PublicKey)
return addr, priv
return sdk.AccAddress(addr.Bytes()), privkey
} }
func newTestSDKTx( func newTestSDKTx(
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey, 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 { ) sdk.Tx {
sigs := make([]auth.StdSignature, len(privs)) sigs := make([]auth.StdSignature, len(privs))
for i, priv := range privs { for i, priv := range privs {
signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "")
sig, err := priv.Sign(signBytes) sig, err := priv.Sign(signBytes)
if err != nil { if err != nil {
panic(err) panic(err)
@ -87,25 +104,23 @@ func newTestSDKTx(
sigs[i] = auth.StdSignature{ sigs[i] = auth.StdSignature{
PubKey: priv.PubKey(), PubKey: priv.PubKey(),
Signature: sig, Signature: sig,
AccountNumber: accNums[i],
Sequence: seqs[i],
} }
} }
return auth.NewStdTx(msgs, fee, sigs, "") 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) chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok { if !ok {
panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID())) panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID()))
} }
privKey, err := crypto.PrivKeyToSecp256k1(priv) privkey, ok := priv.(crypto.PrivKeySecp256k1)
if err != nil { if !ok {
panic(fmt.Sprintf("failed to convert private key: %s", err)) panic(fmt.Sprintf("invalid private key type: %T", priv))
} }
msg.Sign(chainID, privKey) msg.Sign(chainID, privkey.ToECDSA())
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "") return msg
} }

16
crypto/codec.go Normal file
View File

@ -0,0 +1,16 @@
package crypto
import "github.com/cosmos/cosmos-sdk/codec"
var cryptoCodec = codec.New()
func init() {
RegisterCodec(cryptoCodec)
}
// RegisterCodec registers all the necessary types with amino for the given
// codec.
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(PubKeySecp256k1{}, "crypto/PubKeySecp256k1", nil)
cdc.RegisterConcrete(PrivKeySecp256k1{}, "crypto/PrivKeySecp256k1", nil)
}

View File

@ -1,22 +0,0 @@
package crypto
import (
"crypto/ecdsa"
"fmt"
secp256k1 "github.com/tendermint/btcd/btcec"
tmcrypto "github.com/tendermint/tendermint/crypto"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
)
// PrivKeyToSecp256k1 accepts a Tendermint private key and attempts to convert
// it to a SECP256k1 ecdsa.PrivateKey.
func PrivKeyToSecp256k1(priv tmcrypto.PrivKey) (*ecdsa.PrivateKey, error) {
secp256k1Key, ok := priv.(tmsecp256k1.PrivKeySecp256k1)
if !ok {
return nil, fmt.Errorf("invalid private key type: %T", priv)
}
ecdsaPrivKey, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), secp256k1Key[:])
return ecdsaPrivKey.ToECDSA(), nil
}

View File

@ -1,24 +0,0 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/require"
secp256k1 "github.com/tendermint/btcd/btcec"
tmed25519 "github.com/tendermint/tendermint/crypto/ed25519"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
)
func TestPrivKeyToSecp256k1(t *testing.T) {
// require valid SECP256k1 key to convert
secp256k1PrivKey := tmsecp256k1.GenPrivKey()
convertedPriv, err := PrivKeyToSecp256k1(secp256k1PrivKey)
require.NoError(t, err)
require.Equal(t, secp256k1PrivKey[:], (*secp256k1.PrivateKey)(convertedPriv).Serialize())
// require invalid ED25519 key not to convert
ed25519PrivKey := tmed25519.GenPrivKey()
convertedPriv, err = PrivKeyToSecp256k1(ed25519PrivKey)
require.Error(t, err)
require.Nil(t, convertedPriv)
}

105
crypto/secp256k1.go Normal file
View File

@ -0,0 +1,105 @@
package crypto
import (
"bytes"
"crypto/ecdsa"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
ethsecp256k1 "github.com/ethereum/go-ethereum/crypto/secp256k1"
tmcrypto "github.com/tendermint/tendermint/crypto"
)
// ----------------------------------------------------------------------------
// secp256k1 Private Key
var _ tmcrypto.PrivKey = PrivKeySecp256k1{}
// PrivKeySecp256k1 defines a type alias for an ecdsa.PrivateKey that implements
// Tendermint's PrivateKey interface.
type PrivKeySecp256k1 ecdsa.PrivateKey
// GenerateKey generates a new random private key. It returns an error upon
// failure.
func GenerateKey() (PrivKeySecp256k1, error) {
priv, err := ethcrypto.GenerateKey()
if err != nil {
return PrivKeySecp256k1{}, err
}
return PrivKeySecp256k1(*priv), nil
}
// PubKey returns the ECDSA private key's public key.
func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey {
return PubKeySecp256k1{privkey.PublicKey}
}
// Bytes returns the raw ECDSA private key bytes.
func (privkey PrivKeySecp256k1) Bytes() []byte {
return ethcrypto.FromECDSA(privkey.ToECDSA())
}
// Sign creates a recoverable ECDSA signature on the secp256k1 curve over the
// Keccak256 hash of the provided message. The produced signature is 65 bytes
// where the last byte contains the recovery ID.
func (privkey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) {
return ethcrypto.Sign(ethcrypto.Keccak256Hash(msg).Bytes(), privkey.ToECDSA())
}
// Equals returns true if two ECDSA private keys are equal and false otherwise.
func (privkey PrivKeySecp256k1) Equals(other tmcrypto.PrivKey) bool {
if other, ok := other.(PrivKeySecp256k1); ok {
return bytes.Equal(privkey.Bytes(), other.Bytes())
}
return false
}
// ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type.
func (privkey PrivKeySecp256k1) ToECDSA() *ecdsa.PrivateKey {
return (*ecdsa.PrivateKey)(&privkey)
}
// ----------------------------------------------------------------------------
// secp256k1 Public Key
var _ tmcrypto.PubKey = (*PubKeySecp256k1)(nil)
// PubKeySecp256k1 defines a type alias for an ecdsa.PublicKey that implements
// Tendermint's PubKey interface.
type PubKeySecp256k1 struct {
pubkey ecdsa.PublicKey
}
// Address returns the address of the ECDSA public key.
func (key PubKeySecp256k1) Address() tmcrypto.Address {
return tmcrypto.Address(ethcrypto.PubkeyToAddress(key.pubkey).Bytes())
}
// Bytes returns the raw bytes of the ECDSA public key.
func (key PubKeySecp256k1) Bytes() []byte {
return ethcrypto.FromECDSAPub(&key.pubkey)
}
// VerifyBytes verifies that the ECDSA public key created a given signature over
// the provided message. It will calculate the Keccak256 hash of the message
// prior to verification.
func (key PubKeySecp256k1) VerifyBytes(msg []byte, sig []byte) bool {
if len(sig) == 65 {
// remove recovery ID if contained in the signature
sig = sig[:len(sig)-1]
}
// the signature needs to be in [R || S] format when provided to VerifySignature
return ethsecp256k1.VerifySignature(key.Bytes(), ethcrypto.Keccak256Hash(msg).Bytes(), sig)
}
// Equals returns true if two ECDSA public keys are equal and false otherwise.
func (key PubKeySecp256k1) Equals(other tmcrypto.PubKey) bool {
if other, ok := other.(PubKeySecp256k1); ok {
return bytes.Equal(key.Bytes(), other.Bytes())
}
return false
}

61
crypto/secp256k1_test.go Normal file
View File

@ -0,0 +1,61 @@
package crypto
import (
"fmt"
"testing"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
ethsecp256k1 "github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/stretchr/testify/require"
tmcrypto "github.com/tendermint/tendermint/crypto"
)
func TestPrivKeySecp256k1PrivKey(t *testing.T) {
// validate type and equality
privKey, err := GenerateKey()
require.NoError(t, err)
require.True(t, privKey.Equals(privKey))
require.Implements(t, (*tmcrypto.PrivKey)(nil), privKey)
// validate inequality
privKey2, err := GenerateKey()
require.NoError(t, err)
require.False(t, privKey.Equals(privKey2))
// validate Ethereum address equality
addr := privKey.PubKey().Address()
expectedAddr := ethcrypto.PubkeyToAddress(privKey.PublicKey)
require.Equal(t, expectedAddr.Bytes(), addr.Bytes())
// validate we can sign some bytes
msg := []byte("hello world")
sigHash := ethcrypto.Keccak256Hash(msg)
expectedSig, _ := ethsecp256k1.Sign(sigHash.Bytes(), privKey.Bytes())
sig, err := privKey.Sign(msg)
require.NoError(t, err)
require.Equal(t, expectedSig, sig)
}
func TestPrivKeySecp256k1PubKey(t *testing.T) {
privKey, err := GenerateKey()
require.NoError(t, err)
// validate type and equality
pubKey := privKey.PubKey().(PubKeySecp256k1)
require.Implements(t, (*tmcrypto.PubKey)(nil), pubKey)
// validate inequality
privKey2, err := GenerateKey()
require.NoError(t, err)
require.False(t, pubKey.Equals(privKey2.PubKey()))
// validate signature
msg := []byte("hello world")
sig, err := privKey.Sign(msg)
require.NoError(t, err)
fmt.Println("SIG LENGTH:", len(sig))
res := pubKey.VerifyBytes(msg, sig)
require.True(t, res)
}

View File

@ -46,6 +46,8 @@ var (
miner501 = ethcmn.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") miner501 = ethcmn.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D")
genInvestor = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0") genInvestor = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")
paramsKey = sdk.NewKVStoreKey("params")
tParamsKey = sdk.NewTransientStoreKey("transient_params")
accKey = sdk.NewKVStoreKey("acc") accKey = sdk.NewKVStoreKey("acc")
storageKey = sdk.NewKVStoreKey("storage") storageKey = sdk.NewKVStoreKey("storage")
codeKey = sdk.NewKVStoreKey("code") codeKey = sdk.NewKVStoreKey("code")
@ -140,11 +142,7 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak auth.Accoun
ms.Write() ms.Write()
// persist multi-store root state // persist multi-store root state
commitID := cms.Commit() cms.Commit()
require.Equal(
t, "29EF84DF8CC4648FD15341F15585A434279A9514445FC9F9E884D687185C1012",
fmt.Sprintf("%X", commitID.Hash),
)
// verify account mapper state // verify account mapper state
genAcc := ak.GetAccount(ctx, sdk.AccAddress(genInvestor.Bytes())) genAcc := ak.GetAccount(ctx, sdk.AccAddress(genInvestor.Bytes()))
@ -172,12 +170,7 @@ func TestImportBlocks(t *testing.T) {
// create logger, codec and root multi-store // create logger, codec and root multi-store
cdc := newTestCodec() cdc := newTestCodec()
cms := store.NewCommitMultiStore(db) cms := store.NewCommitMultiStore(db)
ak := auth.NewAccountKeeper(cdc, accKey, types.ProtoBaseAccount)
ak := auth.NewAccountKeeper(
cdc,
accKey,
types.ProtoBaseAccount,
)
// mount stores // mount stores
keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey} keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey}

View File

@ -10,15 +10,15 @@ const (
DefaultCodespace sdk.CodespaceType = "ethermint" DefaultCodespace sdk.CodespaceType = "ethermint"
CodeInvalidValue sdk.CodeType = 1 CodeInvalidValue sdk.CodeType = 1
CodeInvalidAccountNumber sdk.CodeType = 2 CodeInvalidChainID sdk.CodeType = 2
) )
func codeToDefaultMsg(code sdk.CodeType) string { func codeToDefaultMsg(code sdk.CodeType) string {
switch code { switch code {
case CodeInvalidValue: case CodeInvalidValue:
return "invalid value" return "invalid value"
case CodeInvalidAccountNumber: case CodeInvalidChainID:
return "invalid account number" return "invalid chain ID"
default: default:
return sdk.CodeToDefaultMsg(code) return sdk.CodeToDefaultMsg(code)
} }
@ -30,8 +30,8 @@ func ErrInvalidValue(msg string) sdk.Error {
return sdk.NewError(DefaultCodespace, CodeInvalidValue, msg) return sdk.NewError(DefaultCodespace, CodeInvalidValue, msg)
} }
// ErrInvalidAccountNumber returns a standardized SDK error resulting from an // ErrInvalidChainID returns a standardized SDK error resulting from an invalid
// invalid account number. // chain ID.
func ErrInvalidAccountNumber(msg string) sdk.Error { func ErrInvalidChainID(msg string) sdk.Error {
return sdk.NewError(DefaultCodespace, CodeInvalidAccountNumber, msg) return sdk.NewError(DefaultCodespace, CodeInvalidChainID, msg)
} }

View File

@ -15,5 +15,5 @@ func init() {
// Register concrete types and interfaces on the given codec. // Register concrete types and interfaces on the given codec.
func RegisterCodec(cdc *codec.Codec) { func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgEthereumTx{}, "ethermint/MsgEthereumTx", nil) cdc.RegisterConcrete(&EthereumTxMsg{}, "ethermint/MsgEthereumTx", nil)
} }

View File

@ -94,7 +94,7 @@ type (
nonceChange struct { nonceChange struct {
account *ethcmn.Address account *ethcmn.Address
prev int64 prev uint64
} }
storageChange struct { storageChange struct {

View File

@ -8,6 +8,7 @@ import (
"math/big" "math/big"
"sync/atomic" "sync/atomic"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/types"
@ -17,17 +18,22 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
var _ sdk.Msg = MsgEthereumTx{} var (
_ sdk.Msg = EthereumTxMsg{}
_ sdk.Tx = EthereumTxMsg{}
)
var big8 = big.NewInt(8)
// message type and route constants // message type and route constants
const ( const (
TypeMsgEthereumTx = "ethereum_tx" TypeEthereumTxMsg = "ethereum_tx"
RouteMsgEthereumTx = "evm" RouteEthereumTxMsg = "evm"
) )
// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message. // EthereumTxMsg encapsulates an Ethereum transaction as an SDK message.
type ( type (
MsgEthereumTx struct { EthereumTxMsg struct {
Data TxData Data TxData
// caches // caches
@ -63,28 +69,28 @@ type (
} }
) )
// NewMsgEthereumTx returns a reference to a new Ethereum transaction message. // NewEthereumTxMsg returns a reference to a new Ethereum transaction message.
func NewMsgEthereumTx( func NewEthereumTxMsg(
nonce uint64, to ethcmn.Address, amount *big.Int, nonce uint64, to ethcmn.Address, amount *big.Int,
gasLimit uint64, gasPrice *big.Int, payload []byte, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *MsgEthereumTx { ) *EthereumTxMsg {
return newMsgEthereumTx(nonce, &to, amount, gasLimit, gasPrice, payload) return newEthereumTxMsg(nonce, &to, amount, gasLimit, gasPrice, payload)
} }
// NewMsgEthereumTxContract returns a reference to a new Ethereum transaction // NewEthereumTxMsgContract returns a reference to a new Ethereum transaction
// message designated for contract creation. // message designated for contract creation.
func NewMsgEthereumTxContract( func NewEthereumTxMsgContract(
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *MsgEthereumTx { ) *EthereumTxMsg {
return newMsgEthereumTx(nonce, nil, amount, gasLimit, gasPrice, payload) return newEthereumTxMsg(nonce, nil, amount, gasLimit, gasPrice, payload)
} }
func newMsgEthereumTx( func newEthereumTxMsg(
nonce uint64, to *ethcmn.Address, amount *big.Int, nonce uint64, to *ethcmn.Address, amount *big.Int,
gasLimit uint64, gasPrice *big.Int, payload []byte, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *MsgEthereumTx { ) *EthereumTxMsg {
if len(payload) > 0 { if len(payload) > 0 {
payload = ethcmn.CopyBytes(payload) payload = ethcmn.CopyBytes(payload)
@ -109,18 +115,18 @@ func newMsgEthereumTx(
txData.Price.Set(gasPrice) txData.Price.Set(gasPrice)
} }
return &MsgEthereumTx{Data: txData} return &EthereumTxMsg{Data: txData}
} }
// Route returns the route value of an MsgEthereumTx. // Route returns the route value of an EthereumTxMsg.
func (msg MsgEthereumTx) Route() string { return RouteMsgEthereumTx } func (msg EthereumTxMsg) Route() string { return RouteEthereumTxMsg }
// Type returns the type value of an MsgEthereumTx. // Type returns the type value of an EthereumTxMsg.
func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx } func (msg EthereumTxMsg) Type() string { return TypeEthereumTxMsg }
// ValidateBasic implements the sdk.Msg interface. It performs basic validation // ValidateBasic implements the sdk.Msg interface. It performs basic validation
// checks of a Transaction. If returns an sdk.Error if validation fails. // checks of a Transaction. If returns an sdk.Error if validation fails.
func (msg MsgEthereumTx) ValidateBasic() sdk.Error { func (msg EthereumTxMsg) ValidateBasic() sdk.Error {
if msg.Data.Price.Sign() != 1 { if msg.Data.Price.Sign() != 1 {
return types.ErrInvalidValue("price must be positive") return types.ErrInvalidValue("price must be positive")
} }
@ -132,12 +138,27 @@ func (msg MsgEthereumTx) ValidateBasic() sdk.Error {
return nil return nil
} }
// 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
}
return msg.Data.Recipient
}
// GetMsgs returns a single EthereumTxMsg as an sdk.Msg.
func (msg EthereumTxMsg) GetMsgs() []sdk.Msg {
return []sdk.Msg{msg}
}
// GetSigners returns the expected signers for an Ethereum transaction message. // GetSigners returns the expected signers for an Ethereum transaction message.
// For such a message, there should exist only a single 'signer'. // For such a message, there should exist only a single 'signer'.
// //
// NOTE: This method cannot be used as a chain ID is needed to recover the signer // NOTE: This method cannot be used as a chain ID is needed to recover the signer
// from the signature. Use 'VerifySig' instead. // from the signature. Use 'VerifySig' instead.
func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress { func (msg EthereumTxMsg) GetSigners() []sdk.AccAddress {
panic("must use 'VerifySig' with a chain ID to get the signer") panic("must use 'VerifySig' with a chain ID to get the signer")
} }
@ -146,13 +167,13 @@ func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress {
// //
// NOTE: This method cannot be used as a chain ID is needed to create valid bytes // NOTE: This method cannot be used as a chain ID is needed to create valid bytes
// to sign over. Use 'RLPSignBytes' instead. // to sign over. Use 'RLPSignBytes' instead.
func (msg MsgEthereumTx) GetSignBytes() []byte { func (msg EthereumTxMsg) GetSignBytes() []byte {
panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign") 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 // RLPSignBytes returns the RLP hash of an Ethereum transaction message with a
// given chainID used for signing. // given chainID used for signing.
func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash { func (msg EthereumTxMsg) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
return rlpHash([]interface{}{ return rlpHash([]interface{}{
msg.Data.AccountNonce, msg.Data.AccountNonce,
msg.Data.Price, msg.Data.Price,
@ -165,12 +186,12 @@ func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
} }
// EncodeRLP implements the rlp.Encoder interface. // EncodeRLP implements the rlp.Encoder interface.
func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error { func (msg *EthereumTxMsg) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &msg.Data) return rlp.Encode(w, &msg.Data)
} }
// DecodeRLP implements the rlp.Decoder interface. // DecodeRLP implements the rlp.Decoder interface.
func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error { func (msg *EthereumTxMsg) DecodeRLP(s *rlp.Stream) error {
_, size, _ := s.Kind() _, size, _ := s.Kind()
err := s.Decode(&msg.Data) err := s.Decode(&msg.Data)
@ -182,7 +203,7 @@ func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
} }
// Hash hashes the RLP encoding of a transaction. // Hash hashes the RLP encoding of a transaction.
func (msg *MsgEthereumTx) Hash() ethcmn.Hash { func (msg *EthereumTxMsg) Hash() ethcmn.Hash {
if hash := msg.hash.Load(); hash != nil { if hash := msg.hash.Load(); hash != nil {
return hash.(ethcmn.Hash) return hash.(ethcmn.Hash)
} }
@ -197,7 +218,7 @@ func (msg *MsgEthereumTx) Hash() ethcmn.Hash {
// takes a private key and chainID to sign an Ethereum transaction according to // 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 // EIP155 standard. It mutates the transaction as it populates the V, R, S
// fields of the Transaction's Signature. // fields of the Transaction's Signature.
func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) { func (msg *EthereumTxMsg) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
txHash := msg.RLPSignBytes(chainID) txHash := msg.RLPSignBytes(chainID)
sig, err := ethcrypto.Sign(txHash[:], priv) sig, err := ethcrypto.Sign(txHash[:], priv)
@ -230,7 +251,7 @@ func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
// VerifySig attempts to verify a Transaction's signature for a given chainID. // 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. // A derived address is returned upon success or an error if recovery fails.
func (msg MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) { func (msg *EthereumTxMsg) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
signer := ethtypes.NewEIP155Signer(chainID) signer := ethtypes.NewEIP155Signer(chainID)
if sc := msg.from.Load(); sc != nil { if sc := msg.from.Load(); sc != nil {
@ -244,43 +265,92 @@ func (msg MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
// do not allow recovery for transactions with an unprotected chainID // do not allow recovery for transactions with an unprotected chainID
if chainID.Sign() == 0 { if chainID.Sign() == 0 {
return ethcmn.Address{}, errors.New("invalid chainID") return ethcmn.Address{}, errors.New("chainID cannot be zero")
} }
txHash := msg.RLPSignBytes(chainID) chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
sig := recoverEthSig(msg.Data.R, msg.Data.S, msg.Data.V, chainID) V := new(big.Int).Sub(msg.Data.V, chainIDMul)
V.Sub(V, big8)
pub, err := ethcrypto.Ecrecover(txHash[:], sig) sigHash := msg.RLPSignBytes(chainID)
sender, err := recoverEthSig(msg.Data.R, msg.Data.S, V, sigHash)
if err != nil { if err != nil {
return ethcmn.Address{}, err return ethcmn.Address{}, err
} }
var addr ethcmn.Address msg.from.Store(sigCache{signer: signer, from: sender})
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:]) return sender, nil
msg.from.Store(sigCache{signer: signer, from: addr})
return addr, nil
} }
// recoverEthSig recovers a signature according to the Ethereum specification. // Cost returns amount + gasprice * gaslimit.
func recoverEthSig(R, S, Vb, chainID *big.Int) []byte { func (msg EthereumTxMsg) Cost() *big.Int {
var v byte total := msg.Fee()
total.Add(total, msg.Data.Amount)
return total
}
// Fee returns gasprice * gaslimit.
func (msg EthereumTxMsg) Fee() *big.Int {
return new(big.Int).Mul(msg.Data.Price, new(big.Int).SetUint64(msg.Data.GasLimit))
}
// ----------------------------------------------------------------------------
// Auxiliary
// TxDecoder returns an sdk.TxDecoder that can decode both auth.StdTx and
// EthereumTxMsg transactions.
func TxDecoder(cdc *codec.Codec) sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx sdk.Tx
if len(txBytes) == 0 {
return nil, sdk.ErrTxDecode("txBytes are empty")
}
err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx)
if err != nil {
fmt.Println(err.Error())
return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error())
}
return tx, nil
}
}
// recoverEthSig recovers a signature according to the Ethereum specification and
// returns the sender or an error.
//
// Ref: Ethereum Yellow Paper (BYZANTIUM VERSION 69351d5) Appendix F
func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, error) {
if Vb.BitLen() > 8 {
return ethcmn.Address{}, errors.New("invalid signature")
}
V := byte(Vb.Uint64() - 27)
if !ethcrypto.ValidateSignatureValues(V, R, S, true) {
return ethcmn.Address{}, errors.New("invalid signature")
}
// encode the signature in uncompressed format
r, s := R.Bytes(), S.Bytes() r, s := R.Bytes(), S.Bytes()
sig := make([]byte, 65) sig := make([]byte, 65)
copy(sig[32-len(r):32], r) copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s) copy(sig[64-len(s):64], s)
sig[64] = V
if chainID.Sign() == 0 { // recover the public key from the signature
v = byte(Vb.Uint64() - 27) pub, err := ethcrypto.Ecrecover(sigHash[:], sig)
} else { if err != nil {
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2)) return ethcmn.Address{}, err
V := new(big.Int).Sub(Vb, chainIDMul)
v = byte(V.Uint64() - 35)
} }
sig[64] = v if len(pub) == 0 || pub[0] != 4 {
return sig return ethcmn.Address{}, errors.New("invalid public key")
}
var addr ethcmn.Address
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
return addr, nil
} }

View File

@ -6,8 +6,8 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/cosmos/ethermint/crypto"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -15,17 +15,17 @@ import (
func TestMsgEthereumTx(t *testing.T) { func TestMsgEthereumTx(t *testing.T) {
addr := GenerateEthAddress() addr := GenerateEthAddress()
msg1 := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test")) msg1 := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
require.NotNil(t, msg1) require.NotNil(t, msg1)
require.Equal(t, *msg1.Data.Recipient, addr) require.Equal(t, *msg1.Data.Recipient, addr)
msg2 := NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test")) msg2 := NewEthereumTxMsgContract(0, nil, 100000, nil, []byte("test"))
require.NotNil(t, msg2) require.NotNil(t, msg2)
require.Nil(t, msg2.Data.Recipient) require.Nil(t, msg2.Data.Recipient)
msg3 := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test")) msg3 := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
require.Equal(t, msg3.Route(), RouteMsgEthereumTx) require.Equal(t, msg3.Route(), RouteEthereumTxMsg)
require.Equal(t, msg3.Type(), TypeMsgEthereumTx) require.Equal(t, msg3.Type(), TypeEthereumTxMsg)
require.Panics(t, func() { msg3.GetSigners() }) require.Panics(t, func() { msg3.GetSigners() })
require.Panics(t, func() { msg3.GetSignBytes() }) require.Panics(t, func() { msg3.GetSignBytes() })
} }
@ -46,7 +46,7 @@ func TestMsgEthereumTxValidation(t *testing.T) {
} }
for i, tc := range testCases { for i, tc := range testCases {
msg := NewMsgEthereumTx(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload) msg := NewEthereumTxMsg(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload)
if tc.expectPass { if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", i) require.Nil(t, msg.ValidateBasic(), "test: %v", i)
@ -60,14 +60,14 @@ func TestMsgEthereumTxRLPSignBytes(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address")) addr := ethcmn.BytesToAddress([]byte("test_address"))
chainID := big.NewInt(3) chainID := big.NewInt(3)
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test")) msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
hash := msg.RLPSignBytes(chainID) hash := msg.RLPSignBytes(chainID)
require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash)) require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash))
} }
func TestMsgEthereumTxRLPEncode(t *testing.T) { func TestMsgEthereumTxRLPEncode(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address")) addr := ethcmn.BytesToAddress([]byte("test_address"))
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test")) msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
raw, err := rlp.EncodeToBytes(msg) raw, err := rlp.EncodeToBytes(msg)
require.NoError(t, err) require.NoError(t, err)
@ -75,11 +75,11 @@ func TestMsgEthereumTxRLPEncode(t *testing.T) {
} }
func TestMsgEthereumTxRLPDecode(t *testing.T) { func TestMsgEthereumTxRLPDecode(t *testing.T) {
var msg MsgEthereumTx var msg EthereumTxMsg
raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080") raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080")
addr := ethcmn.BytesToAddress([]byte("test_address")) addr := ethcmn.BytesToAddress([]byte("test_address"))
expectedMsg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test")) expectedMsg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
err := rlp.Decode(bytes.NewReader(raw), &msg) err := rlp.Decode(bytes.NewReader(raw), &msg)
require.NoError(t, err) require.NoError(t, err)
@ -88,34 +88,46 @@ func TestMsgEthereumTxRLPDecode(t *testing.T) {
func TestMsgEthereumTxHash(t *testing.T) { func TestMsgEthereumTxHash(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address")) addr := ethcmn.BytesToAddress([]byte("test_address"))
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test")) msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
hash := msg.Hash() hash := msg.Hash()
require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash)) require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash))
} }
func TestMsgEthereumTxSig(t *testing.T) { func TestMsgEthereumTxSig(t *testing.T) {
priv, _ := ethcrypto.GenerateKey()
addr := PrivKeyToEthAddress(priv)
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
chainID := big.NewInt(3) chainID := big.NewInt(3)
msg.Sign(chainID, priv) priv1, _ := crypto.GenerateKey()
priv2, _ := crypto.GenerateKey()
addr1 := ethcmn.BytesToAddress(priv1.PubKey().Address().Bytes())
addr2 := ethcmn.BytesToAddress(priv2.PubKey().Address().Bytes())
resultAddr, err := msg.VerifySig(chainID) // require valid signature passes validation
msg := NewEthereumTxMsg(0, addr1, nil, 100000, nil, []byte("test"))
msg.Sign(chainID, priv1.ToECDSA())
signer, err := msg.VerifySig(chainID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, addr, resultAddr) require.Equal(t, addr1, signer)
require.NotEqual(t, addr2, signer)
// require invalid chain ID fail validation
msg = NewEthereumTxMsg(0, addr1, nil, 100000, nil, []byte("test"))
msg.Sign(chainID, priv1.ToECDSA())
signer, err = msg.VerifySig(big.NewInt(4))
require.Error(t, err)
require.Equal(t, ethcmn.Address{}, signer)
} }
func TestMsgEthereumTxAmino(t *testing.T) { func TestMsgEthereumTxAmino(t *testing.T) {
addr := GenerateEthAddress() addr := GenerateEthAddress()
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test")) msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
raw, err := msgCodec.MarshalBinaryBare(msg) raw, err := msgCodec.MarshalBinaryBare(msg)
require.NoError(t, err) require.NoError(t, err)
var msg2 MsgEthereumTx var msg2 EthereumTxMsg
err = msgCodec.UnmarshalBinaryBare(raw, &msg2) err = msgCodec.UnmarshalBinaryBare(raw, &msg2)
require.NoError(t, err) require.NoError(t, err)

View File

@ -176,10 +176,10 @@ func (so *stateObject) SetNonce(nonce uint64) {
prev: so.account.Sequence, prev: so.account.Sequence,
}) })
so.setNonce(int64(nonce)) so.setNonce(nonce)
} }
func (so *stateObject) setNonce(nonce int64) { func (so *stateObject) setNonce(nonce uint64) {
so.account.Sequence = nonce so.account.Sequence = nonce
} }
@ -249,7 +249,7 @@ func (so *stateObject) CodeHash() []byte {
// Nonce returns the state object's current nonce (sequence number). // Nonce returns the state object's current nonce (sequence number).
func (so *stateObject) Nonce() uint64 { func (so *stateObject) Nonce() uint64 {
return uint64(so.account.Sequence) return so.account.Sequence
} }
// Code returns the contract code associated with this object, if any. // Code returns the contract code associated with this object, if any.

View File

@ -1,9 +1,9 @@
package types package types
import ( import (
"crypto/ecdsa"
"fmt" "fmt"
"github.com/cosmos/ethermint/crypto"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto" ethcrypto "github.com/ethereum/go-ethereum/crypto"
ethsha "github.com/ethereum/go-ethereum/crypto/sha3" ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
@ -12,19 +12,14 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// PrivKeyToEthAddress generates an Ethereum address given an ECDSA private key.
func PrivKeyToEthAddress(p *ecdsa.PrivateKey) ethcmn.Address {
return ethcrypto.PubkeyToAddress(p.PublicKey)
}
// GenerateAddress generates an Ethereum address. // GenerateAddress generates an Ethereum address.
func GenerateEthAddress() ethcmn.Address { func GenerateEthAddress() ethcmn.Address {
priv, err := ethcrypto.GenerateKey() priv, err := crypto.GenerateKey()
if err != nil { if err != nil {
panic(err) panic(err)
} }
return PrivKeyToEthAddress(priv) return ethcrypto.PubkeyToAddress(priv.PublicKey)
} }
// ValidateSigner attempts to validate a signer for a given slice of bytes over // ValidateSigner attempts to validate a signer for a given slice of bytes over