TX Routing Refactor (#496)

This commit is contained in:
Alexander Bezobchuk 2018-11-28 17:19:22 -05:00 committed by Jack Zampolin
parent 167d43ce38
commit a3619584f8
24 changed files with 1031 additions and 1032 deletions

89
Gopkg.lock generated
View File

@ -3,11 +3,11 @@
[[projects]]
branch = "master"
digest = "1:495c7006c2f48b705f0d89fd8449a2ae70622bb748788d9d17caafa65a6769f9"
digest = "1:8038f3159385d2017d12ce7d8ccf25e59554a265d76d8c31f8c80acb589da6c6"
name = "github.com/aristanetworks/goarista"
packages = ["monotime"]
pruneopts = "T"
revision = "33151c4543a79b013e8e6799ef45b2ba88c3cd1c"
revision = "5bb443fba8e05f4a819301a63af91fe3cbadcc17"
[[projects]]
branch = "master"
@ -33,7 +33,7 @@
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
[[projects]]
digest = "1:912c494215c339688331953ba09cb9698a4797fe647d93a4e0a117c9c7b960a2"
digest = "1:e70ff0ca07fdc5dfe6cf280917ae4434f069f164a0bf18681e37cb19b01ee151"
name = "github.com/cosmos/cosmos-sdk"
packages = [
"baseapp",
@ -56,7 +56,7 @@
"x/stake/types",
]
pruneopts = "T"
revision = "075ddce79acb77fe88f849f93fb3036e48ffb555"
revision = "1ea0e4c457fc105b48131a60e3d28c6c1bb32cc0"
[[projects]]
digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a"
@ -74,13 +74,6 @@
revision = "cbaa98ba5575e67703b32b4b19f73c91f3c4159e"
version = "v1.7.1"
[[projects]]
digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b"
name = "github.com/ebuchman/fail-test"
packages = ["."]
pruneopts = "T"
revision = "95f809107225be108efcf10a3509e4ea6ceef3c4"
[[projects]]
branch = "master"
digest = "1:67d0b50be0549e610017cb91e0b0b745ec0cad7c613bc8e18ff2d1c1fc8825a7"
@ -378,14 +371,16 @@
version = "v1.0.0"
[[projects]]
digest = "1:f4f3858737fd9db5cf3ef8019c918a798a987d4d11f7e531c54dfe70d4708642"
digest = "1:98aa8bc119587e8bddd558bf2921a645ea6c0ff3195760142113d4dc7cab509f"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/internal",
"prometheus/promhttp",
]
pruneopts = "T"
revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632"
revision = "abad2d1bd44235a26707c172eab6bca5bf2dbad3"
version = "v0.9.1"
[[projects]]
branch = "master"
@ -397,7 +392,7 @@
[[projects]]
branch = "master"
digest = "1:2d9b03513fadf4adf193b3570f5ef65ee57b658d9f11e901a06d17baf2bdc88b"
digest = "1:95442856f5c1df4ff0be91b5a320ee717dd539d4091a3574aeb96bfbd407aa41"
name = "github.com/prometheus/common"
packages = [
"expfmt",
@ -405,7 +400,7 @@
"model",
]
pruneopts = "T"
revision = "7e9e6cabbd393fc208072eedef99188d0ce788b6"
revision = "0b1957f9d949dfa3084171a6ec5642b38055276a"
[[projects]]
branch = "master"
@ -455,12 +450,12 @@
version = "v1.1.2"
[[projects]]
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
name = "github.com/spf13/cast"
packages = ["."]
pruneopts = "T"
revision = "8965335b8c7107321228e3e3702cab9832751bac"
version = "v1.2.0"
revision = "8c9545af88b134710ab1cd196795e7f2388358d7"
version = "v1.3.0"
[[projects]]
digest = "1:52565bd966162d1f4579757f66ce6a7ca9054e7f6b662f0c7c96e4dd228fd017"
@ -508,7 +503,7 @@
[[projects]]
branch = "master"
digest = "1:ea4a45f31f55c7a42ba3063baa646ac94eb7ee9afe60c1fd2c8b396930222620"
digest = "1:fa0605d74039818b662892c950cfd9938ab81ebb5f8e2479ceb4734cdae21df3"
name = "github.com/syndtr/goleveldb"
packages = [
"leveldb",
@ -525,7 +520,7 @@
"leveldb/util",
]
pruneopts = "T"
revision = "6b91fda63f2e36186f1c9d0e48578defb69c5d43"
revision = "f9080354173f192dfc8821931eacf9cfd6819253"
[[projects]]
digest = "1:71ffd1fca92b4972ecd588cf13d9929d4f444659788e9128d055a9126498d41d"
@ -535,35 +530,23 @@
revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df"
[[projects]]
branch = "master"
digest = "1:2b15c0442dc80b581ce7028b2e43029d2f3f985da43cb1d55f7bcdeca785bda0"
name = "github.com/tendermint/ed25519"
packages = [
".",
"edwards25519",
"extra25519",
]
pruneopts = "T"
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
[[projects]]
digest = "1:25c97d29878b5f821bb17a379469f0f923426188241bf2aa81c18728cdc6927c"
digest = "1:bcaa26e82b7707fbdfa6d0506ff1f30158eeb23126f761ac65881ed51339bf9e"
name = "github.com/tendermint/go-amino"
packages = ["."]
pruneopts = "T"
revision = "faa6e731944e2b7b6a46ad202902851e8ce85bee"
version = "v0.12.0"
revision = "6dcc6ddc143e116455c94b25c1004c99e0d0ca12"
version = "v0.14.0"
[[projects]]
digest = "1:2ecd824e1615a8becefea26637fe24576f3800260f5dc91ffe44b37bdbd27878"
digest = "1:1e4b8f8f8c428af22d0cc68b1478bef4e144edefc4bb967d4d09aaddbc8cd71e"
name = "github.com/tendermint/iavl"
packages = ["."]
pruneopts = "T"
revision = "3acc91fb8811db2c5409a855ae1f8e441fe98e2d"
version = "v0.11.0"
revision = "fa74114f764f9827c4ad5573f990ed25bf8c4bac"
version = "v0.11.1"
[[projects]]
digest = "1:b8bd45120cbea639592420b1d5363f102d819ea89d6239f4dae2a0814c76a6d2"
digest = "1:ea9b485e28ee25ef860451187afdec9c38630932e1f999114eb7cab7f8c89966"
name = "github.com/tendermint/tendermint"
packages = [
"abci/client",
@ -592,8 +575,8 @@
"libs/clist",
"libs/common",
"libs/db",
"libs/errors",
"libs/events",
"libs/fail",
"libs/flowrate",
"libs/log",
"libs/pubsub",
@ -614,7 +597,6 @@
"rpc/core",
"rpc/core/types",
"rpc/grpc",
"rpc/lib",
"rpc/lib/client",
"rpc/lib/server",
"rpc/lib/types",
@ -627,16 +609,17 @@
"version",
]
pruneopts = "T"
revision = "90eda9bfb6e6daeed1c8015df41cb36772d91778"
version = "v0.25.1-rc0"
revision = "22dcc92232cd04ce7381043e09d85dd536ae3b96"
version = "v0.26.3"
[[projects]]
branch = "master"
digest = "1:56a43b9f51e5c5ea734e866b82d57c842b022c795a0611ff5f57f3d7c47de45d"
digest = "1:d738326441b0b732070d727891855573dcb579e74d82fcf9a9459d3257f2eb0c"
name = "golang.org/x/crypto"
packages = [
"chacha20poly1305",
"curve25519",
"ed25519",
"ed25519/internal/edwards25519",
"hkdf",
"internal/chacha20",
"internal/subtle",
@ -650,7 +633,8 @@
"ssh/terminal",
]
pruneopts = "T"
revision = "0c41d7ab0a0ee717d4590a44bcb987dfd9e183eb"
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
source = "https://github.com/tendermint/crypto"
[[projects]]
digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7"
@ -673,8 +657,7 @@
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
[[projects]]
branch = "master"
digest = "1:d6d9bf31934efd450aeb0a07061dce8a0e84bc80e4aae05841dd588325832a40"
digest = "1:96672c90ede3a9cd379151c13c436c93efe845c4a3fdd2ce2a94e9c96f233a2c"
name = "golang.org/x/sys"
packages = [
"cpu",
@ -682,7 +665,7 @@
"windows",
]
pruneopts = "T"
revision = "5cd93ef61a7c8f0f858690154eb6de2e69415fa1"
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
[[projects]]
digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a"
@ -721,11 +704,11 @@
[[projects]]
branch = "master"
digest = "1:849525811c9f6ae1f5bd9b866adb4c9436f4a12d767f48e33bf343596d4aafd7"
digest = "1:2460b53d2a66eb9897a17c59ce16c82eeec9affaa31a3ce5814d254abc80fbbd"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "T"
revision = "94acd270e44e65579b9ee3cdab25034d33fed608"
revision = "5fc9ac5403620be16bcdb0c8e7644b1178472c3b"
[[projects]]
digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770"
@ -809,7 +792,11 @@
"github.com/pkg/errors",
"github.com/stretchr/testify/require",
"github.com/stretchr/testify/suite",
"github.com/tendermint/btcd/btcec",
"github.com/tendermint/tendermint/abci/types",
"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/db",
"github.com/tendermint/tendermint/libs/log",

View File

@ -7,9 +7,8 @@
[[constraint]]
name = "github.com/cosmos/cosmos-sdk"
# TODO: Remove this once 0.25 has been released
revision = "075ddce79acb77fe88f849f93fb3036e48ffb555"
# version = "=0.24.2"
# TODO: Replace this with 0.27
revision = "1ea0e4c457fc105b48131a60e3d28c6c1bb32cc0"
[[constraint]]
name = "github.com/hashicorp/golang-lru"
@ -27,23 +26,38 @@
name = "github.com/pkg/errors"
version = "=0.8.0"
# dependecy overrides
[[constraint]]
name = "golang.org/x/net"
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
#######################
# dependecy overrides #
#######################
[[override]]
name = "gopkg.in/fatih/set.v0"
version = "=0.1.0"
[[override]]
name = "github.com/tendermint/go-amino"
version = "v0.14.0"
[[override]]
name = "github.com/tendermint/iavl"
version = "=v0.11.0"
version = "=v0.11.1"
[[override]]
name = "golang.org/x/crypto"
source = "https://github.com/tendermint/crypto"
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
[[override]]
name = "github.com/tendermint/tendermint"
version = "=0.25.1-rc0"
version = "v0.26.1"
[[override]]
name = "github.com/tendermint/go-amino"
version = "=v0.12.0"
name = "golang.org/x/sys"
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
[prune]
go-tests = true

91
app/ante.go Normal file
View File

@ -0,0 +1,91 @@
package app
import (
"encoding/hex"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/tendermint/tendermint/crypto/secp256k1"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
var dummySecp256k1Pubkey secp256k1.PubKeySecp256k1 // used for tx simulation
const (
memoCostPerByte = 1
maxMemoCharacters = 100
secp256k1VerifyCost = 100
)
func init() {
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
// transaction-level processing (e.g. fee payment, signature verification) before
// being passed onto it's respective handler.
//
// NOTE: The EVM will already consume (intrinsic) gas for signature verification
// and covering input size as well as handling nonce incrementing.
func NewAnteHandler(ak auth.AccountKeeper, fck auth.FeeCollectionKeeper) sdk.AnteHandler {
return func(
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
}
// 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
if ethTx, ok := isEthereumTx(stdTx); ethTx != nil && ok {
return ethAnteHandler(ctx, ethTx, ak)
}
return auth.NewAnteHandler(ak, fck)(ctx, stdTx, sim)
}
}
// ----------------------------------------------------------------------------
// 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.
func ethAnteHandler(
ctx sdk.Context, ethTx *evmtypes.MsgEthereumTx, ak auth.AccountKeeper,
) (newCtx sdk.Context, res sdk.Result, abort bool) {
return ctx, sdk.Result{}, false
}
// ----------------------------------------------------------------------------
// Auxiliary
// 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
}
}
return nil, false
}

175
app/ante_test.go Normal file
View File

@ -0,0 +1,175 @@
package app
import (
"fmt"
"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"
)
func requireValidTx(
t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, sim bool,
) {
_, result, abort := anteHandler(ctx, tx, sim)
require.Equal(t, sdk.CodeOK, result.Code, result.Log)
require.False(t, abort)
require.True(t, result.IsOK())
}
func requireInvalidTx(
t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context,
tx sdk.Tx, sim bool, code sdk.CodeType,
) {
newCtx, result, abort := anteHandler(ctx, tx, sim)
require.True(t, abort)
require.Equal(t, code, result.Code, fmt.Sprintf("invalid result: %v", result))
if code == sdk.CodeOutOfGas {
stdTx, ok := tx.(auth.StdTx)
require.True(t, ok, "tx must be in form auth.StdTx")
// require GasWanted is set correctly
require.Equal(t, stdTx.Fee.Gas, result.GasWanted, "'GasWanted' wanted not set correctly")
require.True(t, result.GasUsed > result.GasWanted, "'GasUsed' not greater than GasWanted")
// require that context is set correctly
require.Equal(t, result.GasUsed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly")
}
}
func TestValidTx(t *testing.T) {
setup := newTestSetup()
setup.ctx = setup.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
acc1.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc1)
acc2 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr2)
acc2.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.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()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireValidTx(t, setup.anteHandler, setup.ctx, tx, false)
// require accounts to update
acc1 = setup.accKeeper.GetAccount(setup.ctx, addr1)
acc2 = setup.accKeeper.GetAccount(setup.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)
addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey()
addr3, priv3 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
acc1.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc1)
acc2 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr2)
acc2.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc2)
fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2)
// 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()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.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()}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.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}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnknownAddress)
}
func TestSDKInvalidAcc(t *testing.T) {
setup := newTestSetup()
setup.ctx = setup.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
acc1.SetCoins(newTestCoins())
setup.accKeeper.SetAccount(setup.ctx, acc1)
fee := newTestStdFee()
msg1 := newTestMsg(addr1)
msgs := []sdk.Msg{msg1}
privKeys := []crypto.PrivKey{priv1}
// require validation failure with invalid account number
accNums := []int64{1}
accSeqs := []int64{acc1.GetSequence()}
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence)
// require validation failure with invalid sequence (nonce)
accNums = []int64{acc1.GetAccountNumber()}
accSeqs = []int64{1}
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence)
}
func TestSDKGasConsumption(t *testing.T) {
// TODO: Test gas consumption and OOG once ante handler implementation stabilizes
t.SkipNow()
}

View File

@ -3,7 +3,6 @@ package app
import (
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
@ -11,21 +10,30 @@ import (
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/stake"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
"github.com/pkg/errors"
"github.com/cosmos/ethermint/handlers"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
abci "github.com/tendermint/tendermint/abci/types"
tmcmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
tmlog "github.com/tendermint/tendermint/libs/log"
)
const (
appName = "Ethermint"
const appName = "Ethermint"
// application multi-store keys
var (
storeKeyAccount = sdk.NewKVStoreKey("acc")
storeKeyStorage = sdk.NewKVStoreKey("contract_storage")
storeKeyMain = sdk.NewKVStoreKey("main")
storeKeyStake = sdk.NewKVStoreKey("stake")
storeKeySlashing = sdk.NewKVStoreKey("slashing")
storeKeyGov = sdk.NewKVStoreKey("gov")
storeKeyFeeColl = sdk.NewKVStoreKey("fee")
storeKeyParams = sdk.NewKVStoreKey("params")
storeKeyTransParams = sdk.NewTransientStoreKey("transient_params")
)
type (
@ -59,38 +67,35 @@ type (
// NewEthermintApp returns a reference to a new initialized Ethermint
// application.
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *EthermintApp {
//
// TODO: Ethermint needs to support being bootstrapped as an application running
// in a sovereign zone and as an application running with a shared security model.
// For now, it will support only running as a sovereign application.
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.BaseApp)) *EthermintApp {
cdc := CreateCodec()
cms := store.NewCommitMultiStore(db)
baseAppOpts := []func(*bam.BaseApp){
func(bApp *bam.BaseApp) { bApp.SetCMS(cms) },
}
baseApp := bam.NewBaseApp(appName, logger, db, types.TxDecoder(cdc, sdkAddr), baseAppOpts...)
baseApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOpts...)
app := &EthermintApp{
BaseApp: baseApp,
cdc: cdc,
accountKey: types.StoreKeyAccount,
storageKey: types.StoreKeyStorage,
mainKey: types.StoreKeyMain,
stakeKey: types.StoreKeyStake,
slashingKey: types.StoreKeySlashing,
govKey: types.StoreKeyGov,
feeCollKey: types.StoreKeyFeeColl,
paramsKey: types.StoreKeyParams,
tParamsKey: types.StoreKeyTransParams,
accountKey: storeKeyAccount,
storageKey: storeKeyStorage,
mainKey: storeKeyMain,
stakeKey: storeKeyStake,
slashingKey: storeKeySlashing,
govKey: storeKeyGov,
feeCollKey: storeKeyFeeColl,
paramsKey: storeKeyParams,
tParamsKey: storeKeyTransParams,
}
// set application keepers and mappers
app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.accountKey, auth.ProtoBaseAccount)
app.paramsKeeper = params.NewKeeper(app.cdc, app.paramsKey, app.tParamsKey)
app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.feeCollKey)
// register message handlers
app.Router().
// TODO: Do we need to mount bank and IBC handlers? Should be handled
// directly in the EVM.
// TODO: add remaining routes
AddRoute("stake", stake.NewHandler(app.stakeKeeper)).
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)).
AddRoute("gov", gov.NewHandler(app.govKeeper))
@ -99,7 +104,7 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *Et
app.SetInitChainer(app.initChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)
app.SetAnteHandler(handlers.AnteHandler(app.accountKeeper, app.feeCollKeeper))
app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.feeCollKeeper))
app.MountStoresIAVL(
app.mainKey, app.accountKey, app.stakeKey, app.slashingKey,
@ -117,19 +122,28 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *Et
// BeginBlocker signals the beginning of a block. It performs application
// updates on the start of every block.
func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
func (app *EthermintApp) BeginBlocker(
_ sdk.Context, _ abci.RequestBeginBlock,
) abci.ResponseBeginBlock {
return abci.ResponseBeginBlock{}
}
// EndBlocker signals the end of a block. It performs application updates on
// the end of every block.
func (app *EthermintApp) EndBlocker(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock {
func (app *EthermintApp) EndBlocker(
_ sdk.Context, _ abci.RequestEndBlock,
) abci.ResponseEndBlock {
return abci.ResponseEndBlock{}
}
// initChainer initializes the application blockchain with validators and other
// state data from TendermintCore.
func (app *EthermintApp) initChainer(_ sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
func (app *EthermintApp) initChainer(
_ sdk.Context, req abci.RequestInitChain,
) abci.ResponseInitChain {
var genesisState GenesisState
stateJSON := req.AppStateBytes
@ -148,8 +162,12 @@ func (app *EthermintApp) initChainer(_ sdk.Context, req abci.RequestInitChain) a
func CreateCodec() *codec.Codec {
cdc := codec.New()
types.RegisterCodec(cdc)
// TODO: Add remaining codec registrations:
// bank, staking, distribution, slashing, and gov
evmtypes.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc

View File

@ -1,4 +1,4 @@
package handlers
package app
import (
"github.com/cosmos/cosmos-sdk/store"
@ -6,13 +6,15 @@ import (
dbm "github.com/tendermint/tendermint/libs/db"
)
func createTestMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
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
return ms, capKey, capKey2
}

111
app/test_utils.go Normal file
View File

@ -0,0 +1,111 @@
package app
import (
"fmt"
"math/big"
"time"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/crypto"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
abci "github.com/tendermint/tendermint/abci/types"
tmcrypto "github.com/tendermint/tendermint/crypto"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/tendermint/tendermint/libs/log"
)
var testDenom = "testcoin"
type testSetup struct {
ctx sdk.Context
accKeeper auth.AccountKeeper
feeKeeper auth.FeeCollectionKeeper
anteHandler sdk.AnteHandler
}
func newTestSetup() testSetup {
cdc := codec.New()
ms, capKey, capKey2 := setupMultiStore()
auth.RegisterBaseAccount(cdc)
accKeeper := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
feeKeeper := auth.NewFeeCollectionKeeper(cdc, capKey2)
anteHandler := NewAnteHandler(accKeeper, feeKeeper)
ctx := sdk.NewContext(
ms,
abci.Header{ChainID: "3", Time: time.Now().UTC()},
false,
log.NewNopLogger(),
)
return testSetup{
ctx: ctx,
accKeeper: accKeeper,
feeKeeper: feeKeeper,
anteHandler: anteHandler,
}
}
func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
return sdk.NewTestMsg(addrs...)
}
func newTestCoins() sdk.Coins {
return sdk.Coins{sdk.NewInt64Coin(testDenom, 10000000)}
}
func newTestStdFee() auth.StdFee {
return auth.NewStdFee(5000, sdk.NewInt64Coin(testDenom, 150))
}
// GenerateAddress generates an Ethereum address.
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
priv := tmsecp256k1.GenPrivKey()
addr := sdk.AccAddress(priv.PubKey().Address())
return addr, priv
}
func newTestSDKTx(
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey,
accNums []int64, seqs []int64, 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],
}
}
return auth.NewStdTx(msgs, fee, sigs, "")
}
func newTestEthTx(ctx sdk.Context, msg *evmtypes.MsgEthereumTx, priv tmcrypto.PrivKey) sdk.Tx {
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok {
panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID()))
}
privKey, err := crypto.PrivKeyToSecp256k1(priv)
if err != nil {
panic(fmt.Sprintf("failed to convert private key: %s", err))
}
msg.Sign(chainID, privKey)
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "")
}

22
crypto/crypto.go Normal file
View File

@ -0,0 +1,22 @@
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
}

24
crypto/crypto_test.go Normal file
View File

@ -0,0 +1,24 @@
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)
}

View File

@ -6,58 +6,39 @@ and subject to change.
## Routing
Ethermint needs to parse and handle transactions routed for both the EVM and for
the Cosmos hub. We attempt to achieve this by mimicking [Geth's](https://github.com/ethereum/go-ethereum) `Transaction` structure and utilizing
the `Payload` as the potential encoding of a Cosmos-routed transaction. What
designates this encoding, and ultimately routing, is the `Recipient` address --
if this address matches some global unique predefined and configured address,
we regard it as a transaction meant for Cosmos, otherwise, the transaction is a
pure Ethereum transaction and will be executed in the EVM.
For Cosmos routed transactions, the `Transaction.Payload` will contain an [Amino](https://github.com/tendermint/go-amino) encoded embedded transaction that must
implement the `sdk.Tx` interface. Note, the embedding (outer) `Transaction` is
still RLP encoded in order to preserve compatibility with existing tooling. In
addition, at launch, Ethermint will only support the `auth.StdTx` embedded Cosmos
transaction type.
the Cosmos hub. We attempt to achieve this by mimicking
[Geth's](https://github.com/ethereum/go-ethereum) `Transaction` structure and
treat it as a unique Cosmos SDK message type. An Ethereum transaction is a single
[`sdk.Msg`](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Msg) contained
in an [`auth.StdTx`](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#StdTx).
All relevant Ethereum transaction information is contained in this message. This
includes the signature, gas, payload, etc.
Being that Ethermint implements the Tendermint ABCI application interface, as
transactions are consumed, they are passed through a series of handlers. Once such
handler, `runTx`, is responsible for invoking the `TxDecoder` which performs the
business logic of properly deserializing raw transaction bytes into either an
Ethereum transaction or a Cosmos transaction.
handler, the `AnteHandler`, is responsible for performing preliminary message
execution business logic such as fee payment, signature verification, etc. This is
particular to Cosmos SDK routed transactions. Ethereum routed transactions will
bypass this as the EVM handles the same business logic.
__Note__: Our goal is to utilize Geth as a library, at least as much as possible,
so it should be expected that these types and the operations you may perform on
them will keep in line with Ethereum (e.g. signature algorithms and gas/fees).
In addition, we aim to have existing tooling and frameworks in the Ethereum
ecosystem have 100% compatibility with creating transactions in Ethermint.
Ethereum routed transactions coming from a web3 source are expected to be RLP
encoded, however all internal interaction between Ethermint and Tendermint will
utilize Amino encoding.
## Transactions & Messages
The SDK distinguishes between transactions (`sdk.Tx`) and messages (`sdk.Msg`).
A `sdk.Tx` is a list of `sdk.Msg` wrapped with authentication and fee data. Users
can create messages containing arbitrary information by implementing the `sdk.Msg`
interface.
In Ethermint, the `Transaction` type implements the Cosmos SDK `sdk.Tx` interface.
It addition, it implements the Cosmos SDK `sdk.Msg` interface for the sole purpose
of being to perform basic validation checks in the `BaseApp`. It, however, has
no distinction between transactions and messages.
__Note__: Our goal is to utilize Geth/Turbo-Geth as a library, at least as much
as possible, so it should be expected that these types and the operations you may
perform on them will keep in line with Ethereum (e.g. signature algorithms and
gas/fees). In addition, we aim to have existing tooling and frameworks in the
Ethereum ecosystem have 100% compatibility with creating transactions in Ethermint.
## Signatures
Ethermint supports [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
signatures. A `Transaction` is expected to have a single signature for Ethereum
routed transactions. However, just as in Cosmos, Ethermint will support multiple
signers for embedded Cosmos routed transactions. Signatures over the
`Transaction` type are identical to Ethereum. However, the embedded transaction contains
a canonical signature structure that contains the signature itself and other
information such as an account's sequence number. This, in addition to the chainID,
helps prevent "replay attacks", where the same message could be executed over and
over again.
An embedded transaction's list of signatures must much the unique list of addresses
returned by each message's `GetSigners` call. In addition, the address of first
signer of the embedded transaction is responsible for paying the fees.
signers for non-Ethereum transactions. Signatures over the
`Transaction` type are identical to Ethereum and the signatures will not be duplicated
in the embedding [`auth.StdTx`](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#StdTx).
## Gas & Fees

View File

@ -1,203 +0,0 @@
package handlers
import (
"fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
)
const (
// TODO: Ported from the SDK and may have a different context/value for
// Ethermint.
verifySigCost = 100
)
// internalAnteHandler reflects a function signature an internal ante handler
// must implementing. Internal ante handlers will be dependant upon the
// transaction type.
type internalAnteHandler func(
sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountKeeper,
) (newCtx sdk.Context, res sdk.Result, abort bool)
// AnteHandler is 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.
func AnteHandler(ak auth.AccountKeeper, _ auth.FeeCollectionKeeper) sdk.AnteHandler {
return func(sdkCtx sdk.Context, tx sdk.Tx, _ bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
var (
handler internalAnteHandler
gasLimit int64
)
switch tx := tx.(type) {
case types.Transaction:
gasLimit = int64(tx.Data().GasLimit)
handler = handleEthTx
case auth.StdTx:
gasLimit = tx.Fee.Gas
handler = handleEmbeddedTx
default:
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
}
newCtx = sdkCtx.WithGasMeter(sdk.NewGasMeter(gasLimit))
// 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.
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 = gasLimit
res.GasUsed = newCtx.GasMeter().GasConsumed()
abort = true
default:
panic(r)
}
}
}()
return handler(newCtx, tx, ak)
}
}
// handleEthTx implements an ante handler for an Ethereum transaction. It
// validates the signature and if valid returns an OK result.
func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, ak auth.AccountKeeper) (sdk.Context, sdk.Result, bool) {
ethTx, ok := tx.(types.Transaction)
if !ok {
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
}
// the SDK chainID is a string representation of integer
chainID, ok := new(big.Int).SetString(sdkCtx.ChainID(), 10)
if !ok {
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true
}
sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante: verify Ethereum signature")
addr, err := ethTx.VerifySig(chainID)
if err != nil {
return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true
}
acc := ak.GetAccount(sdkCtx, addr.Bytes())
// validate the account nonce (referred to as sequence in the AccountMapper)
seq := acc.GetSequence()
if ethTx.Data().AccountNonce != uint64(seq) {
return sdkCtx, sdk.ErrInvalidSequence(fmt.Sprintf("invalid account nonce; expected: %d", seq)).Result(), true
}
// TODO: The EVM will handle incrementing the nonce (sequence number)
//
// TODO: Investigate gas consumption models as the EVM instruction set has its
// own and we should probably not charge for additional gas where we don't have
// to.
ak.SetAccount(sdkCtx, acc)
return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data().GasLimit)}, false
}
// handleEmbeddedTx implements an ante handler for an SDK transaction. It
// validates the signature and if valid returns an OK result.
func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, ak auth.AccountKeeper) (sdk.Context, sdk.Result, bool) {
stdTx, ok := tx.(auth.StdTx)
if !ok {
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
}
if err := validateStdTxBasic(stdTx); err != nil {
return sdkCtx, err.Result(), true
}
signerAddrs := stdTx.GetSigners()
signerAccs := make([]auth.Account, len(signerAddrs))
// validate signatures
for i, sig := range stdTx.Signatures {
signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes())
acc, err := validateSignature(sdkCtx, stdTx, signer, sig, ak)
if err != nil {
return sdkCtx, err.Result(), true
}
// TODO: Fees!
ak.SetAccount(sdkCtx, acc)
signerAccs[i] = acc
}
newCtx := auth.WithSigners(sdkCtx, signerAccs)
return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false
}
// validateStdTxBasic validates an auth.StdTx based on parameters that do not
// depend on the context.
func validateStdTxBasic(stdTx auth.StdTx) (err sdk.Error) {
sigs := stdTx.Signatures
if len(sigs) == 0 {
return sdk.ErrUnauthorized("transaction missing signatures")
}
signerAddrs := stdTx.GetSigners()
if len(sigs) != len(signerAddrs) {
return sdk.ErrUnauthorized("invalid number of transaction signers")
}
return nil
}
func validateSignature(
sdkCtx sdk.Context, stdTx auth.StdTx, signer ethcmn.Address,
sig auth.StdSignature, ak auth.AccountKeeper,
) (acc auth.Account, sdkErr sdk.Error) {
chainID := sdkCtx.ChainID()
acc = ak.GetAccount(sdkCtx, signer.Bytes())
if acc == nil {
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("no account with address %s found", signer))
}
accNum := acc.GetAccountNumber()
if accNum != sig.AccountNumber {
return nil, sdk.ErrInvalidSequence(
fmt.Sprintf("invalid account number; got %d, expected %d", sig.AccountNumber, accNum))
}
accSeq := acc.GetSequence()
if accSeq != sig.Sequence {
return nil, sdk.ErrInvalidSequence(
fmt.Sprintf("invalid account sequence; got %d, expected %d", sig.Sequence, accSeq))
}
err := acc.SetSequence(accSeq + 1)
if err != nil {
return nil, sdk.ErrInternal(err.Error())
}
signBytes := types.GetStdTxSignBytes(chainID, accNum, accSeq, stdTx.Fee, stdTx.GetMsgs(), stdTx.Memo)
// consume gas for signature verification
sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante signature verification")
if err := types.ValidateSigner(signBytes, sig.Signature, signer); err != nil {
return nil, sdk.ErrUnauthorized(err.Error())
}
return
}

View File

@ -66,8 +66,10 @@ func init() {
func newTestCodec() *codec.Codec {
cdc := codec.New()
evmtypes.RegisterCodec(cdc)
types.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
@ -139,7 +141,10 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak auth.Accoun
// persist multi-store root state
commitID := cms.Commit()
require.Equal(t, "12D4DB63083D5B01824A35BB70BF671686D60532", fmt.Sprintf("%X", commitID.Hash))
require.Equal(
t, "29EF84DF8CC4648FD15341F15585A434279A9514445FC9F9E884D687185C1012",
fmt.Sprintf("%X", commitID.Hash),
)
// verify account mapper state
genAcc := ak.GetAccount(ctx, sdk.AccAddress(genInvestor.Bytes()))
@ -168,8 +173,7 @@ func TestImportBlocks(t *testing.T) {
cdc := newTestCodec()
cms := store.NewCommitMultiStore(db)
// create account mapper
am := auth.NewAccountKeeper(
ak := auth.NewAccountKeeper(
cdc,
accKey,
types.ProtoBaseAccount,
@ -188,7 +192,7 @@ func TestImportBlocks(t *testing.T) {
require.NoError(t, err)
// set and test genesis block
createAndTestGenesis(t, cms, am)
createAndTestGenesis(t, cms, ak)
// open blockchain export file
blockchainInput, err := os.Open(flagBlockchain)
@ -230,7 +234,7 @@ func TestImportBlocks(t *testing.T) {
ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
ctx = ctx.WithBlockHeight(int64(block.NumberU64()))
stateDB := createStateDB(t, ctx, am)
stateDB := createStateDB(t, ctx, ak)
if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 {
ethmisc.ApplyDAOHardFork(stateDB)

View File

@ -2,7 +2,6 @@ package types
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var typesCodec = codec.New()
@ -14,7 +13,5 @@ func init() {
// RegisterCodec registers all the necessary types with amino for the given
// codec.
func RegisterCodec(cdc *codec.Codec) {
sdk.RegisterCodec(cdc)
cdc.RegisterConcrete(&Transaction{}, "types/Transaction", nil)
cdc.RegisterConcrete(&Account{}, "types/Account", nil)
}

View File

@ -4,25 +4,34 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Ethermint error codes
const (
// DefaultCodespace reserves a Codespace for Ethermint, as 0 and 1 are
// reserved by SDK.
DefaultCodespace sdk.CodespaceType = 2
// DefaultCodespace reserves a Codespace for Ethermint.
DefaultCodespace sdk.CodespaceType = "ethermint"
// CodeInvalidValue reserves the CodeInvalidValue with first non-OK
// codetype.
CodeInvalidValue sdk.CodeType = 1
CodeInvalidAccountNumber sdk.CodeType = 2
)
func codeToDefaultMsg(code sdk.CodeType) string {
switch code {
case CodeInvalidValue:
return "invalid value"
case CodeInvalidAccountNumber:
return "invalid account number"
default:
return sdk.CodeToDefaultMsg(code)
}
}
// ErrInvalidValue returns a standardized SDK error for a given codespace and
// message.
func ErrInvalidValue(codespace sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValue, msg)
// ErrInvalidValue returns a standardized SDK error resulting from an invalid
// value.
func ErrInvalidValue(msg string) sdk.Error {
return sdk.NewError(DefaultCodespace, CodeInvalidValue, msg)
}
// ErrInvalidAccountNumber returns a standardized SDK error resulting from an
// invalid account number.
func ErrInvalidAccountNumber(msg string) sdk.Error {
return sdk.NewError(DefaultCodespace, CodeInvalidAccountNumber, msg)
}

View File

@ -1,16 +0,0 @@
package types
import sdk "github.com/cosmos/cosmos-sdk/types"
// Application multi-store keys
var (
StoreKeyAccount = sdk.NewKVStoreKey("acc")
StoreKeyStorage = sdk.NewKVStoreKey("contract_storage")
StoreKeyMain = sdk.NewKVStoreKey("main")
StoreKeyStake = sdk.NewKVStoreKey("stake")
StoreKeySlashing = sdk.NewKVStoreKey("slashing")
StoreKeyGov = sdk.NewKVStoreKey("gov")
StoreKeyFeeColl = sdk.NewKVStoreKey("fee")
StoreKeyParams = sdk.NewKVStoreKey("params")
StoreKeyTransParams = sdk.NewTransientStoreKey("transient_params")
)

View File

@ -1,103 +0,0 @@
// nolint
package types
import (
"crypto/ecdsa"
"math/big"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
// test variables
var (
TestSDKAddr = GenerateEthAddress()
TestChainID = big.NewInt(3)
TestPrivKey1, _ = ethcrypto.GenerateKey()
TestPrivKey2, _ = ethcrypto.GenerateKey()
TestAddr1 = PrivKeyToEthAddress(TestPrivKey1)
TestAddr2 = PrivKeyToEthAddress(TestPrivKey2)
)
func NewTestCodec() *codec.Codec {
cdc := codec.New()
RegisterCodec(cdc)
auth.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
cdc.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
return cdc
}
func NewTestStdFee() auth.StdFee {
return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150)))
}
func NewTestStdTx(
chainID *big.Int, msgs []sdk.Msg, accNums, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee,
) sdk.Tx {
sigs := make([]auth.StdSignature, len(pKeys))
for i, priv := range pKeys {
signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], NewTestStdFee(), msgs, "")
sig, err := ethcrypto.Sign(signBytes, priv)
if err != nil {
panic(err)
}
sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]}
}
return auth.NewStdTx(msgs, fee, sigs, "")
}
func NewTestGethTxs(
chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey,
) []*ethtypes.Transaction {
txs := make([]*ethtypes.Transaction, len(pKeys))
for i, privKey := range pKeys {
ethTx := ethtypes.NewTransaction(
uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{},
)
signer := ethtypes.NewEIP155Signer(chainID)
ethTx, err := ethtypes.SignTx(ethTx, signer, privKey)
if err != nil {
panic(err)
}
txs[i] = ethTx
}
return txs
}
func NewTestEthTxs(
chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey,
) []*Transaction {
txs := make([]*Transaction, len(pKeys))
for i, privKey := range pKeys {
ethTx := NewTransaction(
uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{},
)
ethTx.Sign(chainID, privKey)
txs[i] = ethTx
}
return txs
}

View File

@ -1,367 +0,0 @@
package types
import (
"bytes"
"crypto/ecdsa"
"fmt"
"io"
"math/big"
"sync/atomic"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp"
"github.com/pkg/errors"
)
// TODO: Move to the EVM module
// message constants
const (
TypeTxEthereum = "Ethereum"
RouteTxEthereum = "evm"
)
// ----------------------------------------------------------------------------
// Ethereum transaction
// ----------------------------------------------------------------------------
var _ sdk.Tx = (*Transaction)(nil)
type (
// Transaction implements the Ethereum transaction structure as an exact
// replica. It implements the Cosmos sdk.Tx interface. Due to the private
// fields, it must be replicated here and cannot be embedded or used
// directly.
//
// Note: The transaction also implements the sdk.Msg interface to perform
// basic validation that is done in the BaseApp.
Transaction struct {
data TxData
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}
// TxData implements the Ethereum transaction data structure as an exact
// copy. It is used solely as intended in Ethereum abiding by the protocol
// except for the payload field which may embed a Cosmos SDK transaction.
TxData struct {
AccountNonce uint64 `json:"nonce"`
Price *big.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value"`
Payload []byte `json:"input"`
// signature values
V *big.Int `json:"v"`
R *big.Int `json:"r"`
S *big.Int `json:"s"`
// hash is only used when marshaling to JSON
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
}
// sigCache is used to cache the derived sender and contains the signer used
// to derive it.
sigCache struct {
signer ethtypes.Signer
from ethcmn.Address
}
)
// NewTransaction returns a reference to a new Ethereum transaction.
func NewTransaction(
nonce uint64, to ethcmn.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *Transaction {
return newTransaction(nonce, &to, amount, gasLimit, gasPrice, payload)
}
// NewContractCreation returns a reference to a new Ethereum transaction
// designated for contract creation.
func NewContractCreation(
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *Transaction {
return newTransaction(nonce, nil, amount, gasLimit, gasPrice, payload)
}
func newTransaction(
nonce uint64, to *ethcmn.Address, amount *big.Int,
gasLimit uint64, gasPrice *big.Int, payload []byte,
) *Transaction {
if len(payload) > 0 {
payload = ethcmn.CopyBytes(payload)
}
txData := TxData{
AccountNonce: nonce,
Recipient: to,
Payload: payload,
GasLimit: gasLimit,
Amount: new(big.Int),
Price: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
}
if amount != nil {
txData.Amount.Set(amount)
}
if gasPrice != nil {
txData.Price.Set(gasPrice)
}
return &Transaction{data: txData}
}
// Data returns the Transaction's data.
func (tx Transaction) Data() TxData {
return tx.data
}
// EncodeRLP implements the rlp.Encoder interface.
func (tx *Transaction) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &tx.data)
}
// DecodeRLP implements the rlp.Decoder interface.
func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
_, size, _ := s.Kind()
err := s.Decode(&tx.data)
if err == nil {
tx.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
}
return err
}
// Hash hashes the RLP encoding of a transaction.
func (tx *Transaction) Hash() ethcmn.Hash {
if hash := tx.hash.Load(); hash != nil {
return hash.(ethcmn.Hash)
}
v := rlpHash(tx)
tx.hash.Store(v)
return v
}
// SigHash returns the RLP hash of a transaction with a given chainID used for
// signing.
func (tx Transaction) SigHash(chainID *big.Int) ethcmn.Hash {
return rlpHash([]interface{}{
tx.data.AccountNonce,
tx.data.Price,
tx.data.GasLimit,
tx.data.Recipient,
tx.data.Amount,
tx.data.Payload,
chainID, uint(0), uint(0),
})
}
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
// takes a private key and chainID to sign an Ethereum transaction according to
// EIP155 standard. It mutates the transaction as it populates the V, R, S
// fields of the Transaction's Signature.
func (tx *Transaction) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
txHash := tx.SigHash(chainID)
sig, err := ethcrypto.Sign(txHash[:], priv)
if err != nil {
panic(err)
}
if len(sig) != 65 {
panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
}
r := new(big.Int).SetBytes(sig[:32])
s := new(big.Int).SetBytes(sig[32:64])
var v *big.Int
if chainID.Sign() == 0 {
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
} else {
v = big.NewInt(int64(sig[64] + 35))
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
v.Add(v, chainIDMul)
}
tx.data.V = v
tx.data.R = r
tx.data.S = s
}
// VerifySig attempts to verify a Transaction's signature for a given chainID.
// A derived address is returned upon success or an error if recovery fails.
func (tx Transaction) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
signer := ethtypes.NewEIP155Signer(chainID)
if sc := tx.from.Load(); sc != nil {
sigCache := sc.(sigCache)
// If the signer used to derive from in a previous
// call is not the same as used current, invalidate
// the cache.
if sigCache.signer.Equal(signer) {
return sigCache.from, nil
}
}
// do not allow recovery for transactions with an unprotected chainID
if chainID.Sign() == 0 {
return ethcmn.Address{}, errors.New("invalid chainID")
}
txHash := tx.SigHash(chainID)
sig := recoverEthSig(tx.data.R, tx.data.S, tx.data.V, chainID)
pub, err := ethcrypto.Ecrecover(txHash[:], sig)
if err != nil {
return ethcmn.Address{}, err
}
var addr ethcmn.Address
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
tx.from.Store(sigCache{signer: signer, from: addr})
return addr, nil
}
// Type implements the sdk.Msg interface. It returns the type of the
// Transaction.
func (tx Transaction) Type() string { return TypeTxEthereum }
// Route implements the sdk.Msg interface. It returns the route of the
// Transaction.
func (tx Transaction) Route() string { return RouteTxEthereum }
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
// checks of a Transaction. If returns an sdk.Error if validation fails.
func (tx Transaction) ValidateBasic() sdk.Error {
if tx.data.Price.Sign() != 1 {
return ErrInvalidValue(DefaultCodespace, "price must be positive")
}
if tx.data.Amount.Sign() != 1 {
return ErrInvalidValue(DefaultCodespace, "amount must be positive")
}
return nil
}
// GetSignBytes performs a no-op and should not be used. It implements the
// sdk.Msg Interface
func (tx Transaction) GetSignBytes() (sigBytes []byte) { return }
// GetSigners performs a no-op and should not be used. It implements the
// sdk.Msg Interface
//
// CONTRACT: The transaction must already be signed.
func (tx Transaction) GetSigners() (signers []sdk.AccAddress) { return }
// GetMsgs returns a single message containing the Transaction itself. It
// implements the Cosmos sdk.Tx interface.
func (tx Transaction) GetMsgs() []sdk.Msg {
return []sdk.Msg{tx}
}
// hasEmbeddedTx returns a boolean reflecting if the transaction contains an
// SDK transaction or not based on the recipient address.
func (tx Transaction) hasEmbeddedTx(addr ethcmn.Address) bool {
return bytes.Equal(tx.data.Recipient.Bytes(), addr.Bytes())
}
// GetEmbeddedTx returns the embedded SDK transaction from an Ethereum
// transaction. It returns an error if decoding the inner transaction fails.
//
// CONTRACT: The payload field of an Ethereum transaction must contain a valid
// encoded SDK transaction.
func (tx Transaction) GetEmbeddedTx(codec *codec.Codec) (sdk.Tx, sdk.Error) {
var etx sdk.Tx
err := codec.UnmarshalBinary(tx.data.Payload, &etx)
if err != nil {
return etx, sdk.ErrTxDecode("failed to decode embedded transaction")
}
return etx, nil
}
// ----------------------------------------------------------------------------
// Utilities
// ----------------------------------------------------------------------------
// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes and an
// SDK address, attempts to decode them into a Transaction or an EmbeddedTx or
// returning an error if decoding fails.
func TxDecoder(codec *codec.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx = Transaction{}
if len(txBytes) == 0 {
return nil, sdk.ErrTxDecode("transaction bytes are empty")
}
err := rlp.DecodeBytes(txBytes, &tx)
if err != nil {
return nil, sdk.ErrTxDecode("failed to decode transaction").TraceSDK(err.Error())
}
// If the transaction is routed as an SDK transaction, decode and return
// the embedded SDK transaction.
if tx.hasEmbeddedTx(sdkAddress) {
etx, err := tx.GetEmbeddedTx(codec)
if err != nil {
return nil, err
}
return etx, nil
}
return tx, nil
}
}
// recoverEthSig recovers a signature according to the Ethereum specification.
func recoverEthSig(R, S, Vb, chainID *big.Int) []byte {
var v byte
r, s := R.Bytes(), S.Bytes()
sig := make([]byte, 65)
copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s)
if chainID.Sign() == 0 {
v = byte(Vb.Uint64() - 27)
} else {
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
V := new(big.Int).Sub(Vb, chainIDMul)
v = byte(V.Uint64() - 35)
}
sig[64] = v
return sig
}
func rlpHash(x interface{}) (hash ethcmn.Hash) {
hasher := ethsha.NewKeccak256()
rlp.Encode(hasher, x)
hasher.Sum(hash[:0])
return
}

View File

@ -1,140 +0,0 @@
package types
import (
"crypto/ecdsa"
"fmt"
"math/big"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require"
)
func TestTransactionRLPEncode(t *testing.T) {
txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
txRLP, err := rlp.EncodeToBytes(txs[0])
require.NoError(t, err)
gtxRLP, err := rlp.EncodeToBytes(gtxs[0])
require.NoError(t, err)
require.Equal(t, gtxRLP, txRLP)
}
func TestTransactionRLPDecode(t *testing.T) {
txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
txRLP, err := rlp.EncodeToBytes(txs[0])
require.NoError(t, err)
gtxRLP, err := rlp.EncodeToBytes(gtxs[0])
require.NoError(t, err)
var (
decodedTx Transaction
decodedGtx ethtypes.Transaction
)
err = rlp.DecodeBytes(txRLP, &decodedTx)
require.NoError(t, err)
err = rlp.DecodeBytes(gtxRLP, &decodedGtx)
require.NoError(t, err)
require.Equal(t, decodedGtx.Hash(), decodedTx.Hash())
}
func TestValidation(t *testing.T) {
ethTxs := NewTestEthTxs(
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
)
testCases := []struct {
msg sdk.Msg
mutate func(sdk.Msg) sdk.Msg
expectedErr bool
}{
{ethTxs[0], func(msg sdk.Msg) sdk.Msg { return msg }, false},
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
tx := msg.(*Transaction)
tx.data.Price = big.NewInt(-1)
return tx
}, true},
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
tx := msg.(*Transaction)
tx.data.Amount = big.NewInt(-1)
return tx
}, true},
}
for i, tc := range testCases {
msg := tc.mutate(tc.msg)
err := msg.ValidateBasic()
if tc.expectedErr {
require.NotEqual(t, sdk.CodeOK, err.Code(), fmt.Sprintf("expected error: test case #%d", i))
} else {
require.NoError(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
}
}
}
func TestTransactionVerifySig(t *testing.T) {
txs := NewTestEthTxs(
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
)
addr, err := txs[0].VerifySig(TestChainID)
require.NoError(t, err)
require.Equal(t, TestAddr1, addr)
addr, err = txs[0].VerifySig(big.NewInt(100))
require.Error(t, err)
require.NotEqual(t, TestAddr1, addr)
}
func TestTxDecoder(t *testing.T) {
testCodec := NewTestCodec()
txDecoder := TxDecoder(testCodec, TestSDKAddr)
msgs := []sdk.Msg{sdk.NewTestMsg()}
// create a non-SDK Ethereum transaction
txs := NewTestEthTxs(
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
)
txBytes, err := rlp.EncodeToBytes(txs[0])
require.NoError(t, err)
// require the transaction to properly decode into a Transaction
decodedTx, err := txDecoder(txBytes)
require.NoError(t, err)
require.IsType(t, Transaction{}, decodedTx)
require.Equal(t, txs[0].data, (decodedTx.(Transaction)).data)
// create a SDK (auth.StdTx) transaction and encode
stdTx := NewTestStdTx(TestChainID, msgs, []int64{0}, []int64{0}, []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee())
payload := testCodec.MustMarshalBinary(stdTx)
tx := NewTransaction(0, TestSDKAddr, big.NewInt(10), 1000, big.NewInt(100), payload)
txBytes, err = rlp.EncodeToBytes(tx)
require.NoError(t, err)
// require the transaction to properly decode into a Transaction
decodedTx, err = txDecoder(txBytes)
require.NoError(t, err)
require.IsType(t, auth.StdTx{}, decodedTx)
require.Equal(t, stdTx, decodedTx)
// require the decoding to fail when no transaction bytes are given
decodedTx, err = txDecoder([]byte{})
require.Error(t, err)
require.Nil(t, decodedTx)
}

View File

@ -1,36 +0,0 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
func TestValidateSigner(t *testing.T) {
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(TestAddr1.Bytes()))}
// create message signing structure and bytes
signBytes := GetStdTxSignBytes(TestChainID.String(), 0, 0, NewTestStdFee(), msgs, "")
// require signing not to fail
sig, err := ethcrypto.Sign(signBytes, TestPrivKey1)
require.NoError(t, err)
// require signature to be valid
err = ValidateSigner(signBytes, sig, TestAddr1)
require.NoError(t, err)
sig, err = ethcrypto.Sign(signBytes, TestPrivKey2)
require.NoError(t, err)
// require signature to be invalid
err = ValidateSigner(signBytes, sig, TestAddr1)
require.Error(t, err)
// require invalid signature bytes return an error
err = ValidateSigner([]byte{}, sig, TestAddr2)
require.Error(t, err)
}

1
x/evm/handler.go Normal file
View File

@ -0,0 +1 @@
package evm

19
x/evm/types/codec.go Normal file
View File

@ -0,0 +1,19 @@
package types
import "github.com/cosmos/cosmos-sdk/codec"
var msgCodec = codec.New()
func init() {
cdc := codec.New()
RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
msgCodec = cdc.Seal()
}
// Register concrete types and interfaces on the given codec.
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgEthereumTx{}, "ethermint/MsgEthereumTx", nil)
}

286
x/evm/types/msg.go Normal file
View File

@ -0,0 +1,286 @@
package types
import (
"crypto/ecdsa"
"errors"
"fmt"
"io"
"math/big"
"sync/atomic"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
var _ sdk.Msg = MsgEthereumTx{}
// message type and route constants
const (
TypeMsgEthereumTx = "ethereum_tx"
RouteMsgEthereumTx = "evm"
)
// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.
type (
MsgEthereumTx struct {
Data TxData
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}
// TxData implements the Ethereum transaction data structure. It is used
// solely as intended in Ethereum abiding by the protocol.
TxData struct {
AccountNonce uint64 `json:"nonce"`
Price *big.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value"`
Payload []byte `json:"input"`
// signature values
V *big.Int `json:"v"`
R *big.Int `json:"r"`
S *big.Int `json:"s"`
// hash is only used when marshaling to JSON
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
}
// sigCache is used to cache the derived sender and contains the signer used
// to derive it.
sigCache struct {
signer ethtypes.Signer
from ethcmn.Address
}
)
// NewMsgEthereumTx returns a reference to a new Ethereum transaction message.
func NewMsgEthereumTx(
nonce uint64, to ethcmn.Address, amount *big.Int,
gasLimit uint64, gasPrice *big.Int, payload []byte,
) *MsgEthereumTx {
return newMsgEthereumTx(nonce, &to, amount, gasLimit, gasPrice, payload)
}
// NewMsgEthereumTxContract returns a reference to a new Ethereum transaction
// message designated for contract creation.
func NewMsgEthereumTxContract(
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *MsgEthereumTx {
return newMsgEthereumTx(nonce, nil, amount, gasLimit, gasPrice, payload)
}
func newMsgEthereumTx(
nonce uint64, to *ethcmn.Address, amount *big.Int,
gasLimit uint64, gasPrice *big.Int, payload []byte,
) *MsgEthereumTx {
if len(payload) > 0 {
payload = ethcmn.CopyBytes(payload)
}
txData := TxData{
AccountNonce: nonce,
Recipient: to,
Payload: payload,
GasLimit: gasLimit,
Amount: new(big.Int),
Price: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
}
if amount != nil {
txData.Amount.Set(amount)
}
if gasPrice != nil {
txData.Price.Set(gasPrice)
}
return &MsgEthereumTx{Data: txData}
}
// Route returns the route value of an MsgEthereumTx.
func (msg MsgEthereumTx) Route() string { return RouteMsgEthereumTx }
// Type returns the type value of an MsgEthereumTx.
func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx }
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
// checks of a Transaction. If returns an sdk.Error if validation fails.
func (msg MsgEthereumTx) ValidateBasic() sdk.Error {
if msg.Data.Price.Sign() != 1 {
return types.ErrInvalidValue("price must be positive")
}
if msg.Data.Amount.Sign() != 1 {
return types.ErrInvalidValue("amount must be positive")
}
return nil
}
// GetSigners returns the expected signers for an Ethereum transaction message.
// 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
// from the signature. Use 'VerifySig' instead.
func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress {
panic("must use 'VerifySig' with a chain ID to get the signer")
}
// GetSignBytes returns the Amino bytes of an Ethereum transaction message used
// for signing.
//
// NOTE: This method cannot be used as a chain ID is needed to create valid bytes
// to sign over. Use 'RLPSignBytes' instead.
func (msg MsgEthereumTx) GetSignBytes() []byte {
panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign")
}
// RLPSignBytes returns the RLP hash of an Ethereum transaction message with a
// given chainID used for signing.
func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
return rlpHash([]interface{}{
msg.Data.AccountNonce,
msg.Data.Price,
msg.Data.GasLimit,
msg.Data.Recipient,
msg.Data.Amount,
msg.Data.Payload,
chainID, uint(0), uint(0),
})
}
// EncodeRLP implements the rlp.Encoder interface.
func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &msg.Data)
}
// DecodeRLP implements the rlp.Decoder interface.
func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
_, size, _ := s.Kind()
err := s.Decode(&msg.Data)
if err == nil {
msg.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
}
return err
}
// Hash hashes the RLP encoding of a transaction.
func (msg *MsgEthereumTx) Hash() ethcmn.Hash {
if hash := msg.hash.Load(); hash != nil {
return hash.(ethcmn.Hash)
}
v := rlpHash(msg)
msg.hash.Store(v)
return v
}
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
// takes a private key and chainID to sign an Ethereum transaction according to
// EIP155 standard. It mutates the transaction as it populates the V, R, S
// fields of the Transaction's Signature.
func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
txHash := msg.RLPSignBytes(chainID)
sig, err := ethcrypto.Sign(txHash[:], priv)
if err != nil {
panic(err)
}
if len(sig) != 65 {
panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
}
r := new(big.Int).SetBytes(sig[:32])
s := new(big.Int).SetBytes(sig[32:64])
var v *big.Int
if chainID.Sign() == 0 {
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
} else {
v = big.NewInt(int64(sig[64] + 35))
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
v.Add(v, chainIDMul)
}
msg.Data.V = v
msg.Data.R = r
msg.Data.S = s
}
// VerifySig attempts to verify a Transaction's signature for a given chainID.
// A derived address is returned upon success or an error if recovery fails.
func (msg MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
signer := ethtypes.NewEIP155Signer(chainID)
if sc := msg.from.Load(); sc != nil {
sigCache := sc.(sigCache)
// If the signer used to derive from in a previous call is not the same as
// used current, invalidate the cache.
if sigCache.signer.Equal(signer) {
return sigCache.from, nil
}
}
// do not allow recovery for transactions with an unprotected chainID
if chainID.Sign() == 0 {
return ethcmn.Address{}, errors.New("invalid chainID")
}
txHash := msg.RLPSignBytes(chainID)
sig := recoverEthSig(msg.Data.R, msg.Data.S, msg.Data.V, chainID)
pub, err := ethcrypto.Ecrecover(txHash[:], sig)
if err != nil {
return ethcmn.Address{}, err
}
var addr ethcmn.Address
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
msg.from.Store(sigCache{signer: signer, from: addr})
return addr, nil
}
// recoverEthSig recovers a signature according to the Ethereum specification.
func recoverEthSig(R, S, Vb, chainID *big.Int) []byte {
var v byte
r, s := R.Bytes(), S.Bytes()
sig := make([]byte, 65)
copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s)
if chainID.Sign() == 0 {
v = byte(Vb.Uint64() - 27)
} else {
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
V := new(big.Int).Sub(Vb, chainIDMul)
v = byte(V.Uint64() - 35)
}
sig[64] = v
return sig
}

123
x/evm/types/msg_test.go Normal file
View File

@ -0,0 +1,123 @@
package types
import (
"bytes"
"fmt"
"math/big"
"testing"
ethcmn "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require"
)
func TestMsgEthereumTx(t *testing.T) {
addr := GenerateEthAddress()
msg1 := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
require.NotNil(t, msg1)
require.Equal(t, *msg1.Data.Recipient, addr)
msg2 := NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test"))
require.NotNil(t, msg2)
require.Nil(t, msg2.Data.Recipient)
msg3 := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
require.Equal(t, msg3.Route(), RouteMsgEthereumTx)
require.Equal(t, msg3.Type(), TypeMsgEthereumTx)
require.Panics(t, func() { msg3.GetSigners() })
require.Panics(t, func() { msg3.GetSignBytes() })
}
func TestMsgEthereumTxValidation(t *testing.T) {
testCases := []struct {
nonce uint64
to ethcmn.Address
amount *big.Int
gasLimit uint64
gasPrice *big.Int
payload []byte
expectPass bool
}{
{amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true},
{amount: big.NewInt(-1), gasPrice: big.NewInt(100000), expectPass: false},
{amount: big.NewInt(100), gasPrice: big.NewInt(-1), expectPass: false},
}
for i, tc := range testCases {
msg := NewMsgEthereumTx(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload)
if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", i)
} else {
require.NotNil(t, msg.ValidateBasic(), "test: %v", i)
}
}
}
func TestMsgEthereumTxRLPSignBytes(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address"))
chainID := big.NewInt(3)
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
hash := msg.RLPSignBytes(chainID)
require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash))
}
func TestMsgEthereumTxRLPEncode(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address"))
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
raw, err := rlp.EncodeToBytes(msg)
require.NoError(t, err)
require.Equal(t, ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080"), raw)
}
func TestMsgEthereumTxRLPDecode(t *testing.T) {
var msg MsgEthereumTx
raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080")
addr := ethcmn.BytesToAddress([]byte("test_address"))
expectedMsg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
err := rlp.Decode(bytes.NewReader(raw), &msg)
require.NoError(t, err)
require.Equal(t, expectedMsg.Data, msg.Data)
}
func TestMsgEthereumTxHash(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address"))
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
hash := msg.Hash()
require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash))
}
func TestMsgEthereumTxSig(t *testing.T) {
priv, _ := ethcrypto.GenerateKey()
addr := PrivKeyToEthAddress(priv)
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
chainID := big.NewInt(3)
msg.Sign(chainID, priv)
resultAddr, err := msg.VerifySig(chainID)
require.NoError(t, err)
require.Equal(t, addr, resultAddr)
}
func TestMsgEthereumTxAmino(t *testing.T) {
addr := GenerateEthAddress()
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
raw, err := msgCodec.MarshalBinaryBare(msg)
require.NoError(t, err)
var msg2 MsgEthereumTx
err = msgCodec.UnmarshalBinaryBare(raw, &msg2)
require.NoError(t, err)
require.Equal(t, msg.Data, msg2.Data)
}

View File

@ -2,17 +2,21 @@ package types
import (
"crypto/ecdsa"
"crypto/sha256"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp"
"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.
func GenerateEthAddress() ethcmn.Address {
priv, err := ethcrypto.GenerateKey()
@ -23,11 +27,6 @@ func GenerateEthAddress() ethcmn.Address {
return PrivKeyToEthAddress(priv)
}
// PrivKeyToEthAddress generates an Ethereum address given an ECDSA private key.
func PrivKeyToEthAddress(p *ecdsa.PrivateKey) ethcmn.Address {
return ethcrypto.PubkeyToAddress(p.PublicKey)
}
// ValidateSigner attempts to validate a signer for a given slice of bytes over
// which a signature and signer is given. An error is returned if address
// derived from the signature and bytes signed does not match the given signer.
@ -43,10 +42,11 @@ func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error {
return nil
}
// GetStdTxSignBytes returns the signature bytes for an auth.StdTx transaction
// that is compatible with Ethereum's signature mechanism.
func GetStdTxSignBytes(chainID string, accNum int64, seq int64, fee auth.StdFee, msgs []sdk.Msg, memo string) []byte {
signBytes := auth.StdSignBytes(chainID, accNum, seq, fee, msgs, "")
hash := sha256.Sum256(signBytes)
return hash[:]
func rlpHash(x interface{}) (hash ethcmn.Hash) {
hasher := ethsha.NewKeccak256()
rlp.Encode(hasher, x)
hasher.Sum(hash[:0])
return
}