Merge pull request #451 from cosmos/aditya/tx

This commit is contained in:
Alexander Bezobchuk 2018-08-08 21:08:54 -04:00 committed by GitHub
commit c1e6f3ea94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1214 additions and 70 deletions

110
Gopkg.lock generated
View File

@ -3,30 +3,29 @@
[[projects]]
branch = "master"
digest = "1:9881039571249d9ff92d4a3383a015c233a4c172f6b5a3d6d71e6c4bfdb70efc"
digest = "1:fcdf62d2d7e43c2565d6f8707ab4eae54dac702ed4bafb194b85139f0508929f"
name = "github.com/aristanetworks/goarista"
packages = ["monotime"]
pruneopts = "T"
revision = "32f94db2e6faa2c7250286dfb4c7ad3dc0f3ead2"
revision = "b2d71c282dc706f4b4f6c15b65810e1202ecd53f"
[[projects]]
branch = "master"
digest = "1:cafb561ce87d0eaa309ad6853380d437df3c1142561c5afa700311825aa38df1"
digest = "1:d4d66abd43dbb9b5f5e6a176c5ed279c289f8db734904c047d95113a04aa2e60"
name = "github.com/btcsuite/btcd"
packages = ["btcec"]
pruneopts = "T"
revision = "fdfc19097e7ac6b57035062056f5b7b4638b8898"
revision = "cf05f92c3f815bbd5091ed6c73eff51f7b1945e8"
[[projects]]
branch = "master"
digest = "1:20d11bf9fd5c6d8f7bb393229fdd981db8b4350aa9a05f28856b64640851c9b8"
digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533"
name = "github.com/btcsuite/btcutil"
packages = ["bech32"]
pruneopts = "T"
revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b"
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
[[projects]]
digest = "1:83f4d10d8cbc8174248ac57b9788ff94f9e3af1999e8708fc50f80b7b91949fb"
digest = "1:36773b598dec105de46a87978ae14e64c8d2c45aa556b8e0ddfc62d6abc7c47e"
name = "github.com/cosmos/cosmos-sdk"
packages = [
"baseapp",
@ -37,11 +36,11 @@
"x/auth",
]
pruneopts = "T"
revision = "1a1373cc220e402397ad536aee6b8f5b068914c6"
version = "v0.21.0"
revision = "23e3d5ac12145c02fcb4b4767d7dfccad782aee5"
version = "v0.23.1"
[[projects]]
digest = "1:3aa953edddec96fd00285789ccd4a31efaff0a2979a3e35b77f5c19d5eaa37f7"
digest = "1:52f195ad0e20a92d8604c1ba3cd246c61644c03eaa454b5acd41be89841e0d10"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "T"
@ -57,7 +56,7 @@
revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e"
[[projects]]
digest = "1:24ae9f4a9d6e2bed9aca667af245834c9a80c39d5ae32e3fc99a2ace91287047"
digest = "1:3238a0c355a81640974751f7d3bab21bf91035165f75c2c457959425c0422a4b"
name = "github.com/ethereum/go-ethereum"
packages = [
"common",
@ -94,7 +93,7 @@
version = "v1.8.11"
[[projects]]
digest = "1:b18534450f89f7007960ff1804d63fb0cc6e7d1989446fcb05d77fb24afc51fc"
digest = "1:0b9c3ad6c948d57a379da9c4e1cdd989b1c73ddc5ec8673f52a9539ce60a109b"
name = "github.com/go-kit/kit"
packages = [
"log",
@ -122,7 +121,7 @@
version = "v1.7.0"
[[projects]]
digest = "1:2a1db9bae44464f781d3637b67df38e896c6e1b9c902e27d24ee9037cb50f23b"
digest = "1:da39f4a22829ca95e63566208e0ea42d6f055f41dff1b14fdab88d88f62df653"
name = "github.com/gogo/protobuf"
packages = [
"gogoproto",
@ -133,11 +132,11 @@
"types",
]
pruneopts = "T"
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
version = "v1.0.0"
revision = "636bf0302bc95575d69441b25a2603156ffdddf1"
version = "v1.1.1"
[[projects]]
digest = "1:6f3df7b8eccb559fa1bda8dae71fdb5f24da5e9aec2696e21f19e6d24062602f"
digest = "1:832e17df5ff8bbe0e0693d2fb46c5e53f96c662ee804049ce3ab6557df74e3ab"
name = "github.com/golang/protobuf"
packages = [
"proto",
@ -147,19 +146,18 @@
"ptypes/timestamp",
]
pruneopts = "T"
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
version = "v1.0.0"
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:968462840e6d86b12990015ac6ab297c022ccde102953040724be1df0e9e6c96"
digest = "1:6027b20c168728321bd99ad01f35118eded457b01c03e647a84833ab331f2f5b"
name = "github.com/golang/snappy"
packages = ["."]
pruneopts = "T"
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]]
branch = "master"
digest = "1:cf296baa185baae04a9a7004efee8511d08e2f5f51d4cbe5375da89722d681db"
name = "github.com/hashicorp/golang-lru"
packages = [
@ -179,7 +177,7 @@
[[projects]]
branch = "master"
digest = "1:14f2079ea27e7c67ecdab4f35e774463abc4f9d1806b5d674c0594b52127ab1d"
digest = "1:dc6b1a6801b3055e9bd3da4cd1e568606eb48118cc6f28e947783aa5d998ad74"
name = "github.com/jmhodges/levigo"
packages = ["."]
pruneopts = "T"
@ -210,15 +208,15 @@
version = "v1.0.0"
[[projects]]
digest = "1:540558c17f78ee4f056aa043cf3389c283b56754db79112a2d64172e80e685db"
digest = "1:6cae6970d70fc5fe75bf83c48ee33e9c4c561a62d0b033254bee8dd5942b815a"
name = "github.com/rs/cors"
packages = ["."]
pruneopts = "T"
revision = "ca016a06a5753f8ba03029c0aa5e54afb1bf713f"
version = "v1.4.0"
revision = "3fb1b69b103a84de38a19c3c6ec073dd6caa4d3f"
version = "v1.5.0"
[[projects]]
digest = "1:daab027a0bfb143afb503f7b63673bfa8d44f69ce9484c6d19b97957aadc1252"
digest = "1:8be8b3743fc9795ec21bbd3e0fc28ff6234018e1a269b0a7064184be95ac13e0"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = "T"
@ -234,7 +232,7 @@
version = "v1.0.1"
[[projects]]
digest = "1:c7f05297d9ad389d81e6d764388d97c4b6a64665eff9fd2550fbdd8545430b80"
digest = "1:e95496462101745805bd4e041a5b841e108c7cf761264d53648246308de2761e"
name = "github.com/stretchr/testify"
packages = [
"assert",
@ -246,7 +244,7 @@
[[projects]]
branch = "master"
digest = "1:9c39a878048f4a5468675b814fb7d2528d622f8c3612511ff0b5e2a48d451ad2"
digest = "1:7d44c4d11eb65cfdc78c76040f37ef305b16474c019c98a8a7cf188fece2d574"
name = "github.com/syndtr/goleveldb"
packages = [
"leveldb",
@ -267,7 +265,7 @@
[[projects]]
branch = "master"
digest = "1:8403202d034640f399279a4f735faabefeb6ee64bbcb03c9c93be1d4c7230382"
digest = "1:2b15c0442dc80b581ce7028b2e43029d2f3f985da43cb1d55f7bcdeca785bda0"
name = "github.com/tendermint/ed25519"
packages = [
".",
@ -278,7 +276,7 @@
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
[[projects]]
digest = "1:4431caadcd2cc6a245bf0f6f61884029f5cc70833a8cc458cd1ba4a578b18c71"
digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245"
name = "github.com/tendermint/go-amino"
packages = ["."]
pruneopts = "T"
@ -286,7 +284,7 @@
version = "0.10.1"
[[projects]]
digest = "1:f11b64e85907d8909a820d0cd793dff19dc268432626411398aa0e26e8a61338"
digest = "1:bf042d2f7d1252b9dcae8e694e2f0a9b5294cb357c086fd86dc540d2f32c9fdf"
name = "github.com/tendermint/iavl"
packages = ["."]
pruneopts = "T"
@ -294,13 +292,16 @@
version = "v0.9.2"
[[projects]]
digest = "1:f056bee4848f0fff6c92e6b99d4972b175d8a8eec9700df30b143b2e6cc48ea8"
digest = "1:9f6704ae2aedbadf616e5850375c504909d46b6ea57d4679de2b7cbc715f08e1"
name = "github.com/tendermint/tendermint"
packages = [
"abci/server",
"abci/types",
"crypto",
"crypto/ed25519",
"crypto/encoding/amino",
"crypto/merkle",
"crypto/secp256k1",
"crypto/tmhash",
"libs/bech32",
"libs/common",
@ -311,28 +312,19 @@
"types",
]
pruneopts = "T"
revision = "5ff65274b84ea905787a48512cc3124385bddf2f"
version = "v0.22.2"
revision = "d542d2c3945116697f60451e6a407082c41c3cc9"
version = "v0.22.8"
[[projects]]
branch = "master"
digest = "1:2c4971d2da7bb27fa225a119dc96af2119dd096869c1228438a0b5fda5f6fe15"
digest = "1:2cbe8758697d867fcebf73bcc69dff8e8abaa7fd65e5704e0744e522ccff4e6a"
name = "golang.org/x/crypto"
packages = [
"internal/subtle",
"nacl/secretbox",
"openpgp/armor",
"openpgp/errors",
"poly1305",
"ripemd160",
"salsa20/salsa",
]
packages = ["ripemd160"]
pruneopts = "T"
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
revision = "f027049dab0ad238e394a753dba2d14753473a04"
[[projects]]
branch = "master"
digest = "1:501f63fec0206818ab3dac086383bd2146841951c6bd6f3c4f72613f113f25fd"
digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7"
name = "golang.org/x/net"
packages = [
"context",
@ -345,10 +337,10 @@
"websocket",
]
pruneopts = "T"
revision = "d0887baf81f4598189d4e12a37c6da86f0bba4d0"
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
[[projects]]
digest = "1:24db346d9931fe01f1e9a02aba78ba22c1ecd55bf0f79dd10ba5169719cf002d"
digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a"
name = "golang.org/x/text"
packages = [
"collate",
@ -371,14 +363,14 @@
version = "v0.3.0"
[[projects]]
digest = "1:cfa1bbb9ee86ade0914bd5f8e8516386cf7d573957191ecb5163d8f6e023ca0c"
digest = "1:8cfa91d1b7f6b66fa9b1a738a4bc1325837b861e63fb9a2919931d68871bb770"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "T"
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
[[projects]]
digest = "1:1faab7c2380bc84698a62531c4af8c9475fbc7b3b1b2696f2f94feff97c47a49"
digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770"
name = "google.golang.org/grpc"
packages = [
".",
@ -390,9 +382,11 @@
"credentials",
"encoding",
"encoding/proto",
"grpclb/grpc_lb_v1/messages",
"grpclog",
"internal",
"internal/backoff",
"internal/channelz",
"internal/grpcrand",
"keepalive",
"metadata",
"naming",
@ -406,8 +400,8 @@
"transport",
]
pruneopts = "T"
revision = "d11072e7ca9811b1100b80ca0269ac831f06d024"
version = "v1.11.3"
revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8"
version = "v1.13.0"
[[projects]]
digest = "1:3ccd10c863188cfe0d936fcfe6a055c95362e43af8e7039e33baade846928e74"
@ -419,7 +413,7 @@
[[projects]]
branch = "v2"
digest = "1:35056a4c53d0b725735422545c3c11bdc9007da2fdb644fee96f3a6b7c42c69f"
digest = "1:dae137be246befa42ce4b48c0feff2c5796b8a5027139a283f31a21173744410"
name = "gopkg.in/karalabe/cookiejar.v2"
packages = ["collections/prque"]
pruneopts = "T"
@ -441,6 +435,7 @@
"github.com/cosmos/cosmos-sdk/store",
"github.com/cosmos/cosmos-sdk/types",
"github.com/cosmos/cosmos-sdk/wire",
"github.com/cosmos/cosmos-sdk/x/auth",
"github.com/ethereum/go-ethereum/common",
"github.com/ethereum/go-ethereum/common/math",
"github.com/ethereum/go-ethereum/consensus",
@ -450,14 +445,19 @@
"github.com/ethereum/go-ethereum/core/state",
"github.com/ethereum/go-ethereum/core/types",
"github.com/ethereum/go-ethereum/core/vm",
"github.com/ethereum/go-ethereum/crypto",
"github.com/ethereum/go-ethereum/crypto/sha3",
"github.com/ethereum/go-ethereum/ethdb",
"github.com/ethereum/go-ethereum/params",
"github.com/ethereum/go-ethereum/rlp",
"github.com/ethereum/go-ethereum/rpc",
"github.com/ethereum/go-ethereum/trie",
"github.com/hashicorp/golang-lru",
"github.com/pkg/errors",
"github.com/stretchr/testify/require",
"github.com/tendermint/tendermint/libs/common",
"github.com/tendermint/tendermint/libs/db",
"github.com/tendermint/tendermint/libs/log",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -4,10 +4,11 @@
[[constraint]]
name = "github.com/cosmos/cosmos-sdk"
version = "=0.21.0"
version = "=0.23.1"
[[constraint]]
name = "github.com/hashicorp/golang-lru"
revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3"
[[constraint]]
name = "github.com/spf13/cobra"
@ -19,11 +20,19 @@
[[override]]
name = "github.com/tendermint/tendermint"
version = "=0.22.2"
version = "=v0.22.8"
[[constraint]]
name = "github.com/stretchr/testify"
version = "=1.2.2"
[[constraint]]
name = "github.com/pkg/errors"
version = "=0.8.0"
[[override]]
name = "gopkg.in/fatih/set.v0"
version = "=0.1.0"
[prune]
go-tests = true

View File

@ -2,38 +2,73 @@ package app
import (
bam "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/handlers"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethparams "github.com/ethereum/go-ethereum/params"
tmcmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
tmlog "github.com/tendermint/tendermint/libs/log"
)
const (
appName = "Ethermint"
)
// EthermintApp implements an extended ABCI application.
type EthermintApp struct {
type (
// EthermintApp implements an extended ABCI application. It is an application
// that may process transactions through Ethereum's EVM running atop of
// Tendermint consensus.
EthermintApp struct {
*bam.BaseApp
codec *wire.Codec
sealed bool
// TODO: stores and keys
// TODO: keepers
// TODO: mappers
accountKey *sdk.KVStoreKey
accountMapper auth.AccountMapper
// TODO: keys, stores, mappers, and keepers
}
// Options is a function signature that provides the ability to modify
// options of an EthermintApp during initialization.
Options func(*EthermintApp)
)
// NewEthermintApp returns a reference to a new initialized Ethermint
// application.
func NewEthermintApp(opts ...func(*EthermintApp)) *EthermintApp {
app := &EthermintApp{}
func NewEthermintApp(
logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig,
sdkAddr ethcmn.Address, opts ...Options,
) *EthermintApp {
// TODO: implement constructor
codec := CreateCodec()
app := &EthermintApp{
BaseApp: bam.NewBaseApp(appName, codec, logger, db),
codec: codec,
accountKey: sdk.NewKVStoreKey("accounts"),
}
app.accountMapper = auth.NewAccountMapper(codec, app.accountKey, auth.ProtoBaseAccount)
app.SetTxDecoder(types.TxDecoder(codec, sdkAddr))
app.SetAnteHandler(handlers.AnteHandler(app.accountMapper))
app.MountStoresIAVL(app.accountKey)
for _, opt := range opts {
opt(app)
}
err := app.LoadLatestVersion(app.accountKey)
if err != nil {
tmcmn.Exit(err.Error())
}
app.seal()
return app
}
@ -43,3 +78,13 @@ func NewEthermintApp(opts ...func(*EthermintApp)) *EthermintApp {
func (app *EthermintApp) seal() {
app.sealed = true
}
// CreateCodec creates a new amino wire codec and registers all the necessary
// structures and interfaces needed for the application.
func CreateCodec() *wire.Codec {
codec := wire.NewCodec()
// Register other modules, types, and messages...
types.RegisterWire(codec)
return codec
}

196
handlers/ante.go Normal file
View File

@ -0,0 +1,196 @@
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"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
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, am auth.AccountMapper,
) (newCtx sdk.Context, res sdk.Result, abort bool)
// AnteHandler handles Ethereum transactions and passes SDK transactions to the
// embeddedAnteHandler if it's an Ethermint transaction. The ante handler gets
// invoked after the BaseApp performs the runTx. At this point, the transaction
// should be properly decoded via the TxDecoder and should be of a proper type,
// Transaction or EmbeddedTx.
func AnteHandler(am auth.AccountMapper) sdk.AnteHandler {
return func(sdkCtx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
var (
gasLimit int64
handler internalAnteHandler
)
switch tx := tx.(type) {
case types.Transaction:
gasLimit = int64(tx.Data.GasLimit)
handler = handleEthTx
case types.EmbeddedTx:
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, am)
}
}
// handleEthTx implements an ante handler for an Ethereum transaction. It
// validates the signature and if valid returns an OK result.
//
// TODO: Do we need to do any further validation or account manipulation
// (e.g. increment nonce)?
func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (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 {
// TODO: ErrInternal may not be correct error to throw here?
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true
}
// validate signature
gethTx := ethTx.ConvertTx(chainID)
signer := ethtypes.NewEIP155Signer(chainID)
_, err := signer.Sender(&gethTx)
if err != nil {
return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true
}
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, am auth.AccountMapper) (sdk.Context, sdk.Result, bool) {
etx, ok := tx.(types.EmbeddedTx)
if !ok {
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
}
if err := validateEmbeddedTxBasic(etx); err != nil {
return sdkCtx, err.Result(), true
}
signerAddrs := etx.GetRequiredSigners()
signerAccs := make([]auth.Account, len(signerAddrs))
// validate signatures
for i, sig := range etx.Signatures {
signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes())
signerAcc, err := validateSignature(sdkCtx, etx, signer, sig, am)
if err.Code() != sdk.CodeOK {
return sdkCtx, err.Result(), false
}
// TODO: Fees!
am.SetAccount(sdkCtx, signerAcc)
signerAccs[i] = signerAcc
}
newCtx := auth.WithSigners(sdkCtx, signerAccs)
return newCtx, sdk.Result{GasWanted: etx.Fee.Gas}, false
}
// validateEmbeddedTxBasic validates an EmbeddedTx based on things that don't
// depend on the context.
func validateEmbeddedTxBasic(etx types.EmbeddedTx) (err sdk.Error) {
sigs := etx.Signatures
if len(sigs) == 0 {
return sdk.ErrUnauthorized("transaction missing signatures")
}
signerAddrs := etx.GetRequiredSigners()
if len(sigs) != len(signerAddrs) {
return sdk.ErrUnauthorized("invalid number of transaction signers")
}
return nil
}
func validateSignature(
sdkCtx sdk.Context, etx types.EmbeddedTx, signer ethcmn.Address,
sig []byte, am auth.AccountMapper,
) (acc auth.Account, sdkErr sdk.Error) {
chainID := sdkCtx.ChainID()
acc = am.GetAccount(sdkCtx, signer.Bytes())
if acc == nil {
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("no account with address %s found", signer))
}
signEtx := types.EmbeddedTxSign{
ChainID: chainID,
AccountNumber: acc.GetAccountNumber(),
Sequence: acc.GetSequence(),
Messages: etx.Messages,
Fee: etx.Fee,
}
err := acc.SetSequence(signEtx.Sequence + 1)
if err != nil {
return nil, sdk.ErrInternal(err.Error())
}
signBytes, err := signEtx.Bytes()
if err != nil {
return nil, sdk.ErrInternal(err.Error())
}
// consume gas for signature verification
sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante verify")
if err := types.ValidateSigner(signBytes, sig, signer); err != nil {
return nil, sdk.ErrUnauthorized(err.Error())
}
return
}

28
types/errors.go Normal file
View File

@ -0,0 +1,28 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
// DefaultCodespace reserves a Codespace for Ethermint, as 0 and 1 are
// reserved by SDK.
DefaultCodespace sdk.CodespaceType = 2
// CodeInvalidValue reserves the CodeInvalidValue with first non-OK
// codetype.
CodeInvalidValue sdk.CodeType = 1
)
func codeToDefaultMsg(code sdk.CodeType) string {
switch code {
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)
}

441
types/tx.go Normal file
View File

@ -0,0 +1,441 @@
package types
import (
"bytes"
"crypto/ecdsa"
"crypto/sha256"
"encoding/json"
"fmt"
"math/big"
"sync/atomic"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common"
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"
)
const (
// TypeTxEthereum reflects an Ethereum Transaction type.
TypeTxEthereum = "Ethereum"
)
// ----------------------------------------------------------------------------
// Ethereum transaction
// ----------------------------------------------------------------------------
type (
// Transaction implements the Ethereum transaction structure as an exact
// copy. 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 sdk.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *ethcmn.Address `json:"to"` // nil means contract creation
Amount sdk.Int `json:"value"`
Payload []byte `json:"input"`
Signature *EthSignature `json:"signature"`
// hash is only used when marshaling to JSON
Hash *ethcmn.Hash `json:"hash"`
}
// EthSignature reflects an Ethereum signature. We wrap this in a structure
// to support Amino serialization of transactions.
EthSignature struct {
v, r, s *big.Int
}
)
// NewEthSignature returns a new instantiated Ethereum signature.
func NewEthSignature(v, r, s *big.Int) *EthSignature {
return &EthSignature{v, r, s}
}
func (es *EthSignature) sanitize() {
if es.v == nil {
es.v = new(big.Int)
}
if es.r == nil {
es.r = new(big.Int)
}
if es.s == nil {
es.s = new(big.Int)
}
}
// MarshalAmino defines a custom encoding scheme for a EthSignature.
func (es EthSignature) MarshalAmino() ([3]string, error) {
es.sanitize()
return ethSigMarshalAmino(es)
}
// UnmarshalAmino defines a custom decoding scheme for a EthSignature.
func (es *EthSignature) UnmarshalAmino(raw [3]string) error {
es.sanitize()
return ethSigUnmarshalAmino(es, raw)
}
// NewTransaction mimics ethereum's NewTransaction function. It returns a
// reference to a new Ethereum Transaction.
func NewTransaction(
nonce uint64, to ethcmn.Address, amount sdk.Int,
gasLimit uint64, gasPrice sdk.Int, payload []byte,
) Transaction {
if len(payload) > 0 {
payload = ethcmn.CopyBytes(payload)
}
txData := TxData{
Recipient: &to,
AccountNonce: nonce,
Payload: payload,
GasLimit: gasLimit,
Amount: amount,
Price: gasPrice,
Signature: NewEthSignature(new(big.Int), new(big.Int), new(big.Int)),
}
return Transaction{Data: txData}
}
// 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 sdk.Int, priv *ecdsa.PrivateKey) {
h := rlpHash([]interface{}{
tx.Data.AccountNonce,
tx.Data.Price.BigInt(),
tx.Data.GasLimit,
tx.Data.Recipient,
tx.Data.Amount.BigInt(),
tx.Data.Payload,
chainID.BigInt(), uint(0), uint(0),
})
sig, err := ethcrypto.Sign(h[:], 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.BigInt(), big.NewInt(2))
v.Add(v, chainIDMul)
}
tx.Data.Signature.v = v
tx.Data.Signature.r = r
tx.Data.Signature.s = s
}
// Type implements the sdk.Msg interface. It returns the type of the
// Transaction.
func (tx Transaction) Type() string {
return TypeTxEthereum
}
// 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}
}
// ConvertTx attempts to converts a Transaction to a new Ethereum transaction
// with the signature set. The signature if first recovered and then a new
// Transaction is created with that signature. If setting the signature fails,
// a panic will be triggered.
func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction {
gethTx := ethtypes.NewTransaction(
tx.Data.AccountNonce, *tx.Data.Recipient, tx.Data.Amount.BigInt(),
tx.Data.GasLimit, tx.Data.Price.BigInt(), tx.Data.Payload,
)
sig := recoverEthSig(tx.Data.Signature, chainID)
signer := ethtypes.NewEIP155Signer(chainID)
gethTx, err := gethTx.WithSignature(signer, sig)
if err != nil {
panic(errors.Wrap(err, "failed to convert transaction with a given signature"))
}
return *gethTx
}
// 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 *wire.Codec) (EmbeddedTx, sdk.Error) {
etx := EmbeddedTx{}
err := codec.UnmarshalBinary(tx.Data.Payload, &etx)
if err != nil {
return EmbeddedTx{}, sdk.ErrTxDecode("failed to encode embedded tx")
}
return etx, nil
}
// ----------------------------------------------------------------------------
// embedded SDK transaction
// ----------------------------------------------------------------------------
type (
// EmbeddedTx implements an SDK transaction. It is to be encoded into the
// payload field of an Ethereum transaction in order to route and handle SDK
// transactions.
EmbeddedTx struct {
Messages []sdk.Msg `json:"messages"`
Fee auth.StdFee `json:"fee"`
Signatures [][]byte `json:"signatures"`
}
// embeddedSignDoc implements a simple SignDoc for a EmbeddedTx signer to
// sign over.
embeddedSignDoc struct {
ChainID string `json:"chainID"`
AccountNumber int64 `json:"accountNumber"`
Sequence int64 `json:"sequence"`
Messages []json.RawMessage `json:"messages"`
Fee json.RawMessage `json:"fee"`
}
// EmbeddedTxSign implements a structure for containing the information
// necessary for building and signing an EmbeddedTx.
EmbeddedTxSign struct {
ChainID string
AccountNumber int64
Sequence int64
Messages []sdk.Msg
Fee auth.StdFee
}
)
// GetMsgs implements the sdk.Tx interface. It returns all the SDK transaction
// messages.
func (etx EmbeddedTx) GetMsgs() []sdk.Msg {
return etx.Messages
}
// GetRequiredSigners returns all the required signers of an SDK transaction
// accumulated from messages. It returns them in a deterministic fashion given
// a list of messages.
func (etx EmbeddedTx) GetRequiredSigners() []sdk.AccAddress {
seen := map[string]bool{}
var signers []sdk.AccAddress
for _, msg := range etx.GetMsgs() {
for _, addr := range msg.GetSigners() {
if !seen[addr.String()] {
signers = append(signers, sdk.AccAddress(addr))
seen[addr.String()] = true
}
}
}
return signers
}
// Bytes returns the EmbeddedTxSign signature bytes for a signer to sign over.
func (ets EmbeddedTxSign) Bytes() ([]byte, error) {
sigBytes, err := EmbeddedSignBytes(ets.ChainID, ets.AccountNumber, ets.Sequence, ets.Messages, ets.Fee)
if err != nil {
return nil, err
}
hash := sha256.Sum256(sigBytes)
return hash[:], nil
}
// EmbeddedSignBytes creates signature bytes for a signer to sign an embedded
// transaction. The signature bytes require a chainID and an account number.
// The signature bytes are JSON encoded.
func EmbeddedSignBytes(chainID string, accnum, sequence int64, msgs []sdk.Msg, fee auth.StdFee) ([]byte, error) {
var msgsBytes []json.RawMessage
for _, msg := range msgs {
msgsBytes = append(msgsBytes, json.RawMessage(msg.GetSignBytes()))
}
signDoc := embeddedSignDoc{
ChainID: chainID,
AccountNumber: accnum,
Sequence: sequence,
Messages: msgsBytes,
Fee: json.RawMessage(fee.Bytes()),
}
bz, err := typesCodec.MarshalJSON(signDoc)
if err != nil {
errors.Wrap(err, "failed to JSON encode EmbeddedSignDoc")
}
return bz, nil
}
// ----------------------------------------------------------------------------
// Utilities
// ----------------------------------------------------------------------------
// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes,
// attempts to decode them into a Transaction or an EmbeddedTx or returning an
// error if decoding fails.
func TxDecoder(codec *wire.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("txBytes are empty")
}
// The given codec should have all the appropriate message types
// registered.
err := codec.UnmarshalBinary(txBytes, &tx)
if err != nil {
return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error())
}
// If the transaction is routed as an SDK transaction, decode and
// return the embedded 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(es *EthSignature, chainID *big.Int) []byte {
var v byte
r, s := es.r.Bytes(), es.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(es.v.Uint64() - 27)
} else {
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
V := new(big.Int).Sub(es.v, chainIDMul)
v = byte(V.Uint64() - 35)
}
sig[64] = v
return sig
}
func rlpHash(x interface{}) (h ethcmn.Hash) {
hasher := ethsha.NewKeccak256()
rlp.Encode(hasher, x)
hasher.Sum(h[:0])
return h
}
func ethSigMarshalAmino(es EthSignature) (raw [3]string, err error) {
vb, err := es.v.MarshalText()
if err != nil {
return raw, err
}
rb, err := es.r.MarshalText()
if err != nil {
return raw, err
}
sb, err := es.s.MarshalText()
if err != nil {
return raw, err
}
raw[0], raw[1], raw[2] = string(vb), string(rb), string(sb)
return raw, err
}
func ethSigUnmarshalAmino(es *EthSignature, raw [3]string) (err error) {
if err = es.v.UnmarshalText([]byte(raw[0])); err != nil {
return
}
if err = es.r.UnmarshalText([]byte(raw[1])); err != nil {
return
}
if err = es.s.UnmarshalText([]byte(raw[2])); err != nil {
return
}
return
}

326
types/tx_test.go Normal file
View File

@ -0,0 +1,326 @@
package types
import (
"crypto/ecdsa"
"fmt"
"math/big"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)
var (
testChainID = sdk.NewInt(3)
testPrivKey1, _ = ethcrypto.GenerateKey()
testPrivKey2, _ = ethcrypto.GenerateKey()
testAddr1 = PrivKeyToEthAddress(testPrivKey1)
testAddr2 = PrivKeyToEthAddress(testPrivKey2)
testSDKAddress = GenerateEthAddress()
)
func newTestCodec() *wire.Codec {
codec := wire.NewCodec()
RegisterWire(codec)
codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
return codec
}
func newStdFee() auth.StdFee {
return auth.NewStdFee(5000, sdk.NewCoin("photon", 150))
}
func newTestEmbeddedTx(
chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey,
accNums []int64, seqs []int64, fee auth.StdFee,
) sdk.Tx {
sigs := make([][]byte, len(pKeys))
for i, priv := range pKeys {
signEtx := EmbeddedTxSign{chainID.String(), accNums[i], seqs[i], msgs, newStdFee()}
signBytes, err := signEtx.Bytes()
if err != nil {
panic(err)
}
sig, err := ethcrypto.Sign(signBytes, priv)
if err != nil {
panic(err)
}
sigs[i] = sig
}
return EmbeddedTx{msgs, fee, sigs}
}
func newTestGethTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []ethtypes.Transaction {
txs := make([]ethtypes.Transaction, len(pKeys))
for i, priv := range pKeys {
ethTx := ethtypes.NewTransaction(
uint64(i), addrs[i], big.NewInt(10), 100, big.NewInt(100), nil,
)
signer := ethtypes.NewEIP155Signer(chainID.BigInt())
ethTx, _ = ethtypes.SignTx(ethTx, signer, priv)
txs[i] = *ethTx
}
return txs
}
func newTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []Transaction {
txs := make([]Transaction, len(pKeys))
for i, priv := range pKeys {
emintTx := NewTransaction(
uint64(i), addrs[i], sdk.NewInt(10), 100, sdk.NewInt(100), nil,
)
emintTx.Sign(chainID, priv)
txs[i] = emintTx
}
return txs
}
func newTestSDKTxs(
codec *wire.Codec, chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey,
accNums []int64, seqs []int64, fee auth.StdFee,
) []Transaction {
txs := make([]Transaction, len(pKeys))
etx := newTestEmbeddedTx(chainID, msgs, pKeys, accNums, seqs, fee)
for i, priv := range pKeys {
payload := codec.MustMarshalBinary(etx)
emintTx := NewTransaction(
uint64(i), testSDKAddress, sdk.NewInt(10), 100,
sdk.NewInt(100), payload,
)
emintTx.Sign(testChainID, priv)
txs[i] = emintTx
}
return txs
}
func TestConvertTx(t *testing.T) {
gethTxs := newTestGethTxs(
testChainID,
[]*ecdsa.PrivateKey{testPrivKey1, testPrivKey2},
[]ethcmn.Address{testAddr1, testAddr2},
)
ethTxs := newTestEthTxs(
testChainID,
[]*ecdsa.PrivateKey{testPrivKey1, testPrivKey2},
[]ethcmn.Address{testAddr1, testAddr2},
)
testCases := []struct {
ethTx ethtypes.Transaction
emintTx Transaction
expectedEq bool
}{
{gethTxs[0], ethTxs[0], true},
{gethTxs[0], ethTxs[1], false},
{gethTxs[1], ethTxs[0], false},
}
for i, tc := range testCases {
convertedTx := tc.emintTx.ConvertTx(testChainID.BigInt())
if tc.expectedEq {
require.Equal(t, tc.ethTx, convertedTx, fmt.Sprintf("unexpected result: test case #%d", i))
} else {
require.NotEqual(t, tc.ethTx, convertedTx, fmt.Sprintf("unexpected result: test case #%d", i))
}
}
}
func TestValidation(t *testing.T) {
ethTxs := newTestEthTxs(
testChainID,
[]*ecdsa.PrivateKey{testPrivKey1},
[]ethcmn.Address{testAddr1},
)
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 = sdk.NewInt(-1)
return tx
}, true},
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
tx := msg.(Transaction)
tx.Data.Amount = sdk.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 TestHasEmbeddedTx(t *testing.T) {
testCodec := newTestCodec()
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
sdkTxs := newTestSDKTxs(
testCodec, testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
[]int64{0}, []int64{0}, newStdFee(),
)
require.True(t, sdkTxs[0].HasEmbeddedTx(testSDKAddress))
ethTxs := newTestEthTxs(
testChainID,
[]*ecdsa.PrivateKey{testPrivKey1},
[]ethcmn.Address{testAddr1},
)
require.False(t, ethTxs[0].HasEmbeddedTx(testSDKAddress))
}
func TestGetEmbeddedTx(t *testing.T) {
testCodec := newTestCodec()
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
ethTxs := newTestEthTxs(
testChainID,
[]*ecdsa.PrivateKey{testPrivKey1},
[]ethcmn.Address{testAddr1},
)
sdkTxs := newTestSDKTxs(
testCodec, testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
[]int64{0}, []int64{0}, newStdFee(),
)
etx, err := sdkTxs[0].GetEmbeddedTx(testCodec)
require.NoError(t, err)
require.NotEmpty(t, etx.Messages)
etx, err = ethTxs[0].GetEmbeddedTx(testCodec)
require.Error(t, err)
require.Empty(t, etx.Messages)
}
func TestTransactionGetMsgs(t *testing.T) {
ethTxs := newTestEthTxs(
testChainID,
[]*ecdsa.PrivateKey{testPrivKey1},
[]ethcmn.Address{testAddr1},
)
msgs := ethTxs[0].GetMsgs()
require.Len(t, msgs, 1)
require.Equal(t, ethTxs[0], msgs[0])
expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
etx := newTestEmbeddedTx(
testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1},
[]int64{0}, []int64{0}, newStdFee(),
)
msgs = etx.GetMsgs()
require.Len(t, msgs, len(expectedMsgs))
require.Equal(t, expectedMsgs, msgs)
}
func TestGetRequiredSigners(t *testing.T) {
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
etx := newTestEmbeddedTx(
testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
[]int64{0}, []int64{0}, newStdFee(),
)
signers := etx.(EmbeddedTx).GetRequiredSigners()
require.Equal(t, []sdk.AccAddress{sdk.AccAddress(testAddr1.Bytes())}, signers)
}
func TestTxDecoder(t *testing.T) {
testCodec := newTestCodec()
txDecoder := TxDecoder(testCodec, testSDKAddress)
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
// create a non-SDK Ethereum transaction
emintTx := NewTransaction(
uint64(0), testAddr1, sdk.NewInt(10), 100, sdk.NewInt(100), nil,
)
emintTx.Sign(testChainID, testPrivKey1)
// require the transaction to properly decode into a Transaction
txBytes := testCodec.MustMarshalBinary(emintTx)
tx, err := txDecoder(txBytes)
require.NoError(t, err)
require.Equal(t, emintTx, tx)
// create embedded transaction and encode
etx := newTestEmbeddedTx(
testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
[]int64{0}, []int64{0}, newStdFee(),
)
payload := testCodec.MustMarshalBinary(etx)
expectedEtx := EmbeddedTx{}
testCodec.UnmarshalBinary(payload, &expectedEtx)
emintTx = NewTransaction(
uint64(0), testSDKAddress, sdk.NewInt(10), 100,
sdk.NewInt(100), payload,
)
emintTx.Sign(testChainID, testPrivKey1)
// require the transaction to properly decode into a Transaction
txBytes = testCodec.MustMarshalBinary(emintTx)
tx, err = txDecoder(txBytes)
require.NoError(t, err)
require.Equal(t, expectedEtx, tx)
// require the decoding to fail when no transaction bytes are given
tx, err = txDecoder([]byte{})
require.Error(t, err)
require.Nil(t, tx)
// create a non-SDK Ethereum transaction with an SDK address and garbage payload
emintTx = NewTransaction(
uint64(0), testSDKAddress, sdk.NewInt(10), 100, sdk.NewInt(100), []byte("garbage"),
)
emintTx.Sign(testChainID, testPrivKey1)
// require the transaction to fail decoding as the payload is invalid
txBytes = testCodec.MustMarshalBinary(emintTx)
tx, err = txDecoder(txBytes)
require.Error(t, err)
require.Nil(t, tx)
}

41
types/utils.go Normal file
View File

@ -0,0 +1,41 @@
package types
import (
"crypto/ecdsa"
"fmt"
ethcmn "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/pkg/errors"
)
// GenerateAddress generates an Ethereum address.
func GenerateEthAddress() ethcmn.Address {
priv, err := ethcrypto.GenerateKey()
if err != nil {
panic(err)
}
return PrivKeyToEthAddress(priv)
}
// PrivKeyToEthAddress generates an Ethereum address given an ECDSA private key.
func PrivKeyToEthAddress(p *ecdsa.PrivateKey) ethcmn.Address {
return ethcrypto.PubkeyToAddress(ecdsa.PublicKey(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.
func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error {
pk, err := ethcrypto.SigToPub(signBytes, sig)
if err != nil {
return errors.Wrap(err, "signature verification failed")
} else if ethcrypto.PubkeyToAddress(*pk) != signer {
return fmt.Errorf("invalid signature for signer: %s", signer)
}
return nil
}

36
types/utils_test.go Normal file
View File

@ -0,0 +1,36 @@
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
signEtx := EmbeddedTxSign{testChainID.String(), 0, 0, msgs, newStdFee()}
// create signing bytes and sign
signBytes, err := signEtx.Bytes()
require.NoError(t, err)
// 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)
}

22
types/wire.go Normal file
View File

@ -0,0 +1,22 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
var typesCodec = wire.NewCodec()
func init() {
RegisterWire(typesCodec)
}
// RegisterWire registers all the necessary types with amino for the given
// codec.
func RegisterWire(codec *wire.Codec) {
sdk.RegisterWire(codec)
codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil)
codec.RegisterConcrete(TxData{}, "types/TxData", nil)
codec.RegisterConcrete(Transaction{}, "types/Transaction", nil)
codec.RegisterConcrete(EmbeddedTx{}, "types/EmbeddedTx", nil)
}