Merge pull request #475 from cosmos/bez/474-refactor-tx-impl

R4R: Remove EmbeddedTx and Related Logic
This commit is contained in:
Jack Zampolin 2018-08-24 09:22:54 -07:00 committed by GitHub
commit 50e0602923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 112 additions and 434 deletions

54
Gopkg.lock generated
View File

@ -3,19 +3,19 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:fcdf62d2d7e43c2565d6f8707ab4eae54dac702ed4bafb194b85139f0508929f" digest = "1:79b02529d2120af44eaad5f7fee9ff7e003739d469e2c2767008b797d290e9fd"
name = "github.com/aristanetworks/goarista" name = "github.com/aristanetworks/goarista"
packages = ["monotime"] packages = ["monotime"]
pruneopts = "T" pruneopts = "T"
revision = "b2d71c282dc706f4b4f6c15b65810e1202ecd53f" revision = "18b896026201d8e1758468d9c5d5a558c69c5e9b"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:d4d66abd43dbb9b5f5e6a176c5ed279c289f8db734904c047d95113a04aa2e60" digest = "1:8ad24ea05e770b745b5c286b4f94cf73d5be87005680e36b2d0dd1de0a2f9fbf"
name = "github.com/btcsuite/btcd" name = "github.com/btcsuite/btcd"
packages = ["btcec"] packages = ["btcec"]
pruneopts = "T" pruneopts = "T"
revision = "cf05f92c3f815bbd5091ed6c73eff51f7b1945e8" revision = "d81d8877b8f327112e94e814937143a71d1692a7"
[[projects]] [[projects]]
digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533" digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533"
@ -25,7 +25,7 @@
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
[[projects]] [[projects]]
digest = "1:36773b598dec105de46a87978ae14e64c8d2c45aa556b8e0ddfc62d6abc7c47e" digest = "1:bc28e755cf6a9fd8e65497514d20c4907973e7a6a6409d30ead3fd37bfeb19a9"
name = "github.com/cosmos/cosmos-sdk" name = "github.com/cosmos/cosmos-sdk"
packages = [ packages = [
"baseapp", "baseapp",
@ -36,16 +36,16 @@
"x/auth", "x/auth",
] ]
pruneopts = "T" pruneopts = "T"
revision = "23e3d5ac12145c02fcb4b4767d7dfccad782aee5" revision = "1c38c70468ec721e3a555ba2f3bf5f9da31f0cc9"
version = "v0.23.1" version = "v0.24.2"
[[projects]] [[projects]]
digest = "1:52f195ad0e20a92d8604c1ba3cd246c61644c03eaa454b5acd41be89841e0d10" digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a"
name = "github.com/davecgh/go-spew" name = "github.com/davecgh/go-spew"
packages = ["spew"] packages = ["spew"]
pruneopts = "T" pruneopts = "T"
revision = "346938d642f2ec3594ed81d874461961cd0faa76" revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.0" version = "v1.1.1"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -224,12 +224,12 @@
version = "v0.0.3" version = "v0.0.3"
[[projects]] [[projects]]
digest = "1:6de2f73eb31e80d74f84ce1c861e4c0c8f00ca5fb41a25901f987e63a0647c28" digest = "1:9ba911fe3884995431690e7eb180cf848da0d637ba5f61711783b795d031793f"
name = "github.com/spf13/pflag" name = "github.com/spf13/pflag"
packages = ["."] packages = ["."]
pruneopts = "T" pruneopts = "T"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd" revision = "9a97c102cda95a86cec2345a6f09f55a939babf5"
version = "v1.0.1" version = "v1.0.2"
[[projects]] [[projects]]
digest = "1:e95496462101745805bd4e041a5b841e108c7cf761264d53648246308de2761e" digest = "1:e95496462101745805bd4e041a5b841e108c7cf761264d53648246308de2761e"
@ -245,7 +245,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:7d44c4d11eb65cfdc78c76040f37ef305b16474c019c98a8a7cf188fece2d574" digest = "1:ee395d0d8c1719b5a1407f34af93953b4763bacb19a8961aba5b6d312824da41"
name = "github.com/syndtr/goleveldb" name = "github.com/syndtr/goleveldb"
packages = [ packages = [
"leveldb", "leveldb",
@ -262,7 +262,7 @@
"leveldb/util", "leveldb/util",
] ]
pruneopts = "T" pruneopts = "T"
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -276,14 +276,6 @@
pruneopts = "T" pruneopts = "T"
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
[[projects]]
branch = "master"
digest = "1:3a6bdd02e7f2585c860e368467c5989310740af6206a1ada85cfa19c712e5afd"
name = "github.com/tendermint/ethermint"
packages = ["version"]
pruneopts = "T"
revision = "c1e6ebf80a6cc9119bc178faee18ef13490d707a"
[[projects]] [[projects]]
digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245" digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245"
name = "github.com/tendermint/go-amino" name = "github.com/tendermint/go-amino"
@ -301,7 +293,7 @@
version = "v0.9.2" version = "v0.9.2"
[[projects]] [[projects]]
digest = "1:9f6704ae2aedbadf616e5850375c504909d46b6ea57d4679de2b7cbc715f08e1" digest = "1:5a60cb048b401c0263c227baf8778ecaf038be531707057607949540486874ef"
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
packages = [ packages = [
"abci/server", "abci/server",
@ -321,16 +313,16 @@
"types", "types",
] ]
pruneopts = "T" pruneopts = "T"
revision = "d542d2c3945116697f60451e6a407082c41c3cc9" revision = "81df19e68ab1519399fccf0cab81cb75bf9d782e"
version = "v0.22.8" version = "v0.23.1-rc0"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:2cbe8758697d867fcebf73bcc69dff8e8abaa7fd65e5704e0744e522ccff4e6a" digest = "1:da29cbeb9d244918393b37243c008ab7128688fb017c966aaf876587c010bcdd"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
packages = ["ripemd160"] packages = ["ripemd160"]
pruneopts = "T" pruneopts = "T"
revision = "f027049dab0ad238e394a753dba2d14753473a04" revision = "614d502a4dac94afa3a6ce146bd1736da82514c6"
[[projects]] [[projects]]
digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7" digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7"
@ -372,11 +364,12 @@
version = "v0.3.0" version = "v0.3.0"
[[projects]] [[projects]]
digest = "1:8cfa91d1b7f6b66fa9b1a738a4bc1325837b861e63fb9a2919931d68871bb770" branch = "master"
digest = "1:960f1fa3f12667fe595c15c12523718ed8b1b5428c83d70da54bb014da9a4c1a"
name = "google.golang.org/genproto" name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"] packages = ["googleapis/rpc/status"]
pruneopts = "T" pruneopts = "T"
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" revision = "c66870c02cf823ceb633bcd05be3c7cda29976f4"
[[projects]] [[projects]]
digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770" digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770"
@ -466,7 +459,6 @@
"github.com/pkg/errors", "github.com/pkg/errors",
"github.com/stretchr/testify/require", "github.com/stretchr/testify/require",
"github.com/stretchr/testify/suite", "github.com/stretchr/testify/suite",
"github.com/tendermint/ethermint/version",
"github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/common",
"github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/db",
"github.com/tendermint/tendermint/libs/log", "github.com/tendermint/tendermint/libs/log",

View File

@ -4,7 +4,7 @@
[[constraint]] [[constraint]]
name = "github.com/cosmos/cosmos-sdk" name = "github.com/cosmos/cosmos-sdk"
version = "=0.23.1" version = "=0.24.2"
[[constraint]] [[constraint]]
name = "github.com/hashicorp/golang-lru" name = "github.com/hashicorp/golang-lru"
@ -15,12 +15,12 @@
version = "~0.0.1" version = "~0.0.1"
[[override]] [[override]]
name = "google.golang.org/genproto" name = "github.com/tendermint/iavl"
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" version = "=v0.9.2"
[[override]] [[override]]
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
version = "=v0.22.8" version = "=v0.23.1-rc0"
[[constraint]] [[constraint]]
name = "github.com/stretchr/testify" name = "github.com/stretchr/testify"

View File

@ -9,7 +9,6 @@ import (
"github.com/cosmos/ethermint/handlers" "github.com/cosmos/ethermint/handlers"
"github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethparams "github.com/ethereum/go-ethereum/params" ethparams "github.com/ethereum/go-ethereum/params"
tmcmn "github.com/tendermint/tendermint/libs/common" tmcmn "github.com/tendermint/tendermint/libs/common"
@ -43,20 +42,17 @@ type (
// NewEthermintApp returns a reference to a new initialized Ethermint // NewEthermintApp returns a reference to a new initialized Ethermint
// application. // application.
func NewEthermintApp( func NewEthermintApp(logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, opts ...Options,
logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig,
sdkAddr ethcmn.Address, opts ...Options,
) *EthermintApp { ) *EthermintApp {
codec := CreateCodec() codec := CreateCodec()
app := &EthermintApp{ app := &EthermintApp{
BaseApp: bam.NewBaseApp(appName, codec, logger, db), BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec)),
codec: codec, codec: codec,
accountKey: sdk.NewKVStoreKey("accounts"), accountKey: sdk.NewKVStoreKey("accounts"),
} }
app.accountMapper = auth.NewAccountMapper(codec, app.accountKey, auth.ProtoBaseAccount) app.accountMapper = auth.NewAccountMapper(codec, app.accountKey, auth.ProtoBaseAccount)
app.SetTxDecoder(types.TxDecoder(codec, sdkAddr))
app.SetAnteHandler(handlers.AnteHandler(app.accountMapper)) app.SetAnteHandler(handlers.AnteHandler(app.accountMapper))
app.MountStoresIAVL(app.accountKey) app.MountStoresIAVL(app.accountKey)

View File

@ -6,34 +6,20 @@ and subject to change.
## Routing ## Routing
Ethermint needs to parse and handle transactions routed for both the EVM and for 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 Cosmos hub. We attempt to achieve this by mimicking [Geth's](https://github.com/ethereum/go-ethereum) `Transaction` structure to handle
the `Payload` as the potential encoding of a Cosmos-routed transaction. What Ethereum transactions and utilizing the SDK's `auth.StdTx` for Cosmos
designates this encoding, and ultimately routing, is the `Transaction.Recipient` transactions. Both of these structures are registered with an [Amino](https://github.com/tendermint/go-amino) codec, so the `TxDecoder` that in invoked
address -- if this address matches some global unique predefined and configured during the `BaseApp#runTx`, will be able to decode raw transaction bytes into the
address, we regard it as a transaction meant for Cosmos, otherwise, the transaction appropriate transaction type which will then be passed onto handlers downstream.
is a pure Ethereum transaction and will be executed in the EVM.
For Cosmos routed transactions, the `Transaction.Payload` will contain an __Note__: Our goal is to utilize Geth as a library, at least as much as possible,
embedded encoded type: `EmbeddedTx`. This structure is analogous to the Cosmos so it should be expected that these types and the operations you may perform on
SDK `sdk.StdTx`. If a client wishes to send an `EmbeddedTx`, it must first encode them will keep in line with Ethereum (e.g. signature algorithms and gas/fees).
the embedded transaction, and then encode the embedding `Transaction`.
__Note__: The `Transaction` and `EmbeddedTx` types utilize the [Amino](https://github.com/tendermint/go-amino) object serialization protocol and as such,
the `Transaction` is not an exact replica of what will be found in Ethereum. 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.
Being that Ethermint implements the ABCI application interface, as transactions
are sent 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.
## Transactions & Messages ## Transactions & Messages
The SDK distinguishes between transactions (`sdk.Tx`) and messages (`sdk.Msg`). The SDK distinguishes between transactions (`sdk.Tx`) and messages (`sdk.Msg`).
A `sdk.Tx` is a list of `sdk.Msg`s wrapped with authentication and fee data. Users 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` can create messages containing arbitrary information by implementing the `sdk.Msg`
interface. interface.
@ -42,25 +28,21 @@ It addition, it implements the Cosmos SDK `sdk.Msg` interface for the sole purpo
of being to perform basic validation checks in the `BaseApp`. It, however, has of being to perform basic validation checks in the `BaseApp`. It, however, has
no distinction between transactions and messages. no distinction between transactions and messages.
The `EmbeddedTx`, being analogous to the Cosmos SDK `sdk.StdTx`, implements the
Cosmos SDK `sdk.Tx` interface.
## Signatures ## Signatures
Ethermint supports [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) 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 signatures. A `Transaction` is expected to have a single signature for Ethereum
routed transactions. However, just as in Cosmos, Ethermint will support multiple routed transactions. However, just as in Cosmos, Ethermint will support multiple
signers for `EmbeddedTx` Cosmos routed transactions. Signatures over the signers for `auth.StdTx` Cosmos routed transactions. Signatures over the
`Transaction` type are identical to Ethereum. However, the `EmbeddedTx` contains `Transaction` type are identical to Ethereum. However, the `auth.StdTx` contains
a canonical signature structure that contains the signature itself and other a canonical signature structure that contains the signature itself and other
information such as an account's sequence number. The sequence number is expected information such as an account's sequence number. This, in addition to the chainID,
to increment every time a message is signed by a given account. This, in addition helps prevent "replay attacks", where the same message could be executed over and
to the chain ID, prevents "replay attacks", where the same message could be over again.
executed over and over again.
An `EmbeddedTx` list of signatures must much the unique list of addresses returned An `auth.StdTx` 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 by each message's `GetSigners` call. In addition, the address of first signer of
the `EmbeddedTx` is responsible for paying the fees. the `auth.StdTx` is responsible for paying the fees.
## Gas & Fees ## Gas & Fees

View File

@ -26,11 +26,10 @@ type internalAnteHandler func(
sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper, sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper,
) (newCtx sdk.Context, res sdk.Result, abort bool) ) (newCtx sdk.Context, res sdk.Result, abort bool)
// AnteHandler handles Ethereum transactions and passes SDK transactions to the // AnteHandler is responsible for attempting to route an Ethereum or SDK
// embeddedAnteHandler if it's an Ethermint transaction. The ante handler gets // transaction to an internal ante handler for performing transaction-level
// invoked after the BaseApp performs the runTx. At this point, the transaction // processing (e.g. fee payment, signature verification) before being passed
// should be properly decoded via the TxDecoder and should be of a proper type, // onto it's respective handler.
// Transaction or EmbeddedTx.
func AnteHandler(am auth.AccountMapper) sdk.AnteHandler { func AnteHandler(am auth.AccountMapper) sdk.AnteHandler {
return func(sdkCtx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return func(sdkCtx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
var ( var (
@ -42,7 +41,7 @@ func AnteHandler(am auth.AccountMapper) sdk.AnteHandler {
case types.Transaction: case types.Transaction:
gasLimit = int64(tx.Data.GasLimit) gasLimit = int64(tx.Data.GasLimit)
handler = handleEthTx handler = handleEthTx
case types.EmbeddedTx: case auth.StdTx:
gasLimit = tx.Fee.Gas gasLimit = tx.Fee.Gas
handler = handleEmbeddedTx handler = handleEmbeddedTx
default: default:
@ -107,23 +106,23 @@ func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk.Cont
// handleEmbeddedTx implements an ante handler for an SDK transaction. It // handleEmbeddedTx implements an ante handler for an SDK transaction. It
// validates the signature and if valid returns an OK result. // 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) { func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk.Context, sdk.Result, bool) {
etx, ok := tx.(types.EmbeddedTx) stdTx, ok := tx.(auth.StdTx)
if !ok { if !ok {
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
} }
if err := validateEmbeddedTxBasic(etx); err != nil { if err := validateStdTxBasic(stdTx); err != nil {
return sdkCtx, err.Result(), true return sdkCtx, err.Result(), true
} }
signerAddrs := etx.GetRequiredSigners() signerAddrs := stdTx.GetSigners()
signerAccs := make([]auth.Account, len(signerAddrs)) signerAccs := make([]auth.Account, len(signerAddrs))
// validate signatures // validate signatures
for i, sig := range etx.Signatures { for i, sig := range stdTx.Signatures {
signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes()) signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes())
signerAcc, err := validateSignature(sdkCtx, etx, signer, sig, am) signerAcc, err := validateSignature(sdkCtx, stdTx, signer, sig, am)
if err.Code() != sdk.CodeOK { if err.Code() != sdk.CodeOK {
return sdkCtx, err.Result(), false return sdkCtx, err.Result(), false
} }
@ -136,18 +135,18 @@ func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk
newCtx := auth.WithSigners(sdkCtx, signerAccs) newCtx := auth.WithSigners(sdkCtx, signerAccs)
return newCtx, sdk.Result{GasWanted: etx.Fee.Gas}, false return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false
} }
// validateEmbeddedTxBasic validates an EmbeddedTx based on things that don't // validateStdTxBasic validates an auth.StdTx based on parameters that do not
// depend on the context. // depend on the context.
func validateEmbeddedTxBasic(etx types.EmbeddedTx) (err sdk.Error) { func validateStdTxBasic(stdTx auth.StdTx) (err sdk.Error) {
sigs := etx.Signatures sigs := stdTx.Signatures
if len(sigs) == 0 { if len(sigs) == 0 {
return sdk.ErrUnauthorized("transaction missing signatures") return sdk.ErrUnauthorized("transaction missing signatures")
} }
signerAddrs := etx.GetRequiredSigners() signerAddrs := stdTx.GetSigners()
if len(sigs) != len(signerAddrs) { if len(sigs) != len(signerAddrs) {
return sdk.ErrUnauthorized("invalid number of transaction signers") return sdk.ErrUnauthorized("invalid number of transaction signers")
} }
@ -156,8 +155,8 @@ func validateEmbeddedTxBasic(etx types.EmbeddedTx) (err sdk.Error) {
} }
func validateSignature( func validateSignature(
sdkCtx sdk.Context, etx types.EmbeddedTx, signer ethcmn.Address, sdkCtx sdk.Context, stdTx auth.StdTx, signer ethcmn.Address,
sig []byte, am auth.AccountMapper, sig auth.StdSignature, am auth.AccountMapper,
) (acc auth.Account, sdkErr sdk.Error) { ) (acc auth.Account, sdkErr sdk.Error) {
chainID := sdkCtx.ChainID() chainID := sdkCtx.ChainID()
@ -167,28 +166,29 @@ func validateSignature(
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("no account with address %s found", signer)) return nil, sdk.ErrUnknownAddress(fmt.Sprintf("no account with address %s found", signer))
} }
signEtx := types.EmbeddedTxSign{ accNum := acc.GetAccountNumber()
ChainID: chainID, if accNum != sig.AccountNumber {
AccountNumber: acc.GetAccountNumber(), return nil, sdk.ErrInvalidSequence(
Sequence: acc.GetSequence(), fmt.Sprintf("invalid account number; got %d, expected %d", sig.AccountNumber, accNum))
Messages: etx.Messages,
Fee: etx.Fee,
} }
err := acc.SetSequence(signEtx.Sequence + 1) 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 { if err != nil {
return nil, sdk.ErrInternal(err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
signBytes, err := signEtx.Bytes() signBytes := types.GetStdTxSignBytes(chainID, accNum, accSeq, stdTx.Fee, stdTx.GetMsgs(), stdTx.Memo)
if err != nil {
return nil, sdk.ErrInternal(err.Error())
}
// consume gas for signature verification // consume gas for signature verification
sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante verify") sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante signature verification")
if err := types.ValidateSigner(signBytes, sig, signer); err != nil { if err := types.ValidateSigner(signBytes, sig.Signature, signer); err != nil {
return nil, sdk.ErrUnauthorized(err.Error()) return nil, sdk.ErrUnauthorized(err.Error())
} }

View File

@ -1,17 +1,13 @@
package types package types
import ( import (
"bytes"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/sha256"
"encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"sync/atomic" "sync/atomic"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto" ethcrypto "github.com/ethereum/go-ethereum/crypto"
@ -26,13 +22,9 @@ const (
TypeTxEthereum = "Ethereum" TypeTxEthereum = "Ethereum"
) )
// ----------------------------------------------------------------------------
// Ethereum transaction
// ----------------------------------------------------------------------------
type ( type (
// Transaction implements the Ethereum transaction structure as an exact // Transaction implements the Ethereum transaction structure as an exact
// copy. It implements the Cosmos sdk.Tx interface. Due to the private // replica. It implements the Cosmos sdk.Tx interface. Due to the private
// fields, it must be replicated here and cannot be embedded or used // fields, it must be replicated here and cannot be embedded or used
// directly. // directly.
// //
@ -47,9 +39,7 @@ type (
from atomic.Value from atomic.Value
} }
// TxData implements the Ethereum transaction data structure as an exact // TxData defines internal Ethereum transaction information
// 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 { TxData struct {
AccountNonce uint64 `json:"nonce"` AccountNonce uint64 `json:"nonce"`
Price sdk.Int `json:"gasPrice"` Price sdk.Int `json:"gasPrice"`
@ -204,6 +194,8 @@ func (tx Transaction) GetMsgs() []sdk.Msg {
// with the signature set. The signature if first recovered and then a new // with the signature set. The signature if first recovered and then a new
// Transaction is created with that signature. If setting the signature fails, // Transaction is created with that signature. If setting the signature fails,
// a panic will be triggered. // a panic will be triggered.
//
// TODO: To be removed in #470
func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction { func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction {
gethTx := ethtypes.NewTransaction( gethTx := ethtypes.NewTransaction(
tx.Data.AccountNonce, *tx.Data.Recipient, tx.Data.Amount.BigInt(), tx.Data.AccountNonce, *tx.Data.Recipient, tx.Data.Amount.BigInt(),
@ -221,139 +213,16 @@ func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction {
return *gethTx 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, // TxDecoder returns an sdk.TxDecoder that given raw transaction bytes,
// attempts to decode them into a Transaction or an EmbeddedTx or returning an // attempts to decode them into a valid sdk.Tx.
// error if decoding fails. func TxDecoder(codec *wire.Codec) sdk.TxDecoder {
func TxDecoder(codec *wire.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, sdk.Error) { return func(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx = Transaction{}
if len(txBytes) == 0 { if len(txBytes) == 0 {
return nil, sdk.ErrTxDecode("txBytes are empty") return nil, sdk.ErrTxDecode("txBytes are empty")
} }
var tx sdk.Tx
// The given codec should have all the appropriate message types // The given codec should have all the appropriate message types
// registered. // registered.
err := codec.UnmarshalBinary(txBytes, &tx) err := codec.UnmarshalBinary(txBytes, &tx)
@ -361,17 +230,6 @@ func TxDecoder(codec *wire.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder {
return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error()) 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 return tx, nil
} }
} }

View File

@ -3,14 +3,12 @@ package types
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"math/big"
"testing" "testing"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto" ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -23,64 +21,42 @@ var (
testAddr1 = PrivKeyToEthAddress(testPrivKey1) testAddr1 = PrivKeyToEthAddress(testPrivKey1)
testAddr2 = PrivKeyToEthAddress(testPrivKey2) testAddr2 = PrivKeyToEthAddress(testPrivKey2)
testSDKAddress = GenerateEthAddress()
) )
func newTestCodec() *wire.Codec { func newTestCodec() *wire.Codec {
codec := wire.NewCodec() codec := wire.NewCodec()
RegisterWire(codec) RegisterWire(codec)
codec.RegisterConcrete(auth.StdTx{}, "test/StdTx", nil)
codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
wire.RegisterCrypto(codec)
return codec return codec
} }
func newStdFee() auth.StdFee { func newStdFee() auth.StdFee {
return auth.NewStdFee(5000, sdk.NewCoin("photon", 150)) return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150)))
} }
func newTestEmbeddedTx( func newTestStdTx(
chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey, chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey,
accNums []int64, seqs []int64, fee auth.StdFee, accNums []int64, seqs []int64, fee auth.StdFee,
) sdk.Tx { ) sdk.Tx {
sigs := make([][]byte, len(pKeys)) sigs := make([]auth.StdSignature, len(pKeys))
for i, priv := range pKeys { for i, priv := range pKeys {
signEtx := EmbeddedTxSign{chainID.String(), accNums[i], seqs[i], msgs, newStdFee()} signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], newStdFee(), msgs, "")
signBytes, err := signEtx.Bytes()
if err != nil {
panic(err)
}
sig, err := ethcrypto.Sign(signBytes, priv) sig, err := ethcrypto.Sign(signBytes, priv)
if err != nil { if err != nil {
panic(err) panic(err)
} }
sigs[i] = sig sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]}
} }
return EmbeddedTx{msgs, fee, sigs} return auth.NewStdTx(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 { func newTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []Transaction {
@ -99,63 +75,6 @@ func newTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Ad
return txs 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) { func TestValidation(t *testing.T) {
ethTxs := newTestEthTxs( ethTxs := newTestEthTxs(
testChainID, testChainID,
@ -193,47 +112,6 @@ func TestValidation(t *testing.T) {
} }
} }
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) { func TestTransactionGetMsgs(t *testing.T) {
ethTxs := newTestEthTxs( ethTxs := newTestEthTxs(
testChainID, testChainID,
@ -246,7 +124,7 @@ func TestTransactionGetMsgs(t *testing.T) {
require.Equal(t, ethTxs[0], msgs[0]) require.Equal(t, ethTxs[0], msgs[0])
expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
etx := newTestEmbeddedTx( etx := newTestStdTx(
testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1}, testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1},
[]int64{0}, []int64{0}, newStdFee(), []int64{0}, []int64{0}, newStdFee(),
) )
@ -256,21 +134,10 @@ func TestTransactionGetMsgs(t *testing.T) {
require.Equal(t, expectedMsgs, msgs) 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) { func TestTxDecoder(t *testing.T) {
testCodec := newTestCodec() testCodec := newTestCodec()
txDecoder := TxDecoder(testCodec, testSDKAddress) txDecoder := TxDecoder(testCodec)
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} msgs := []sdk.Msg{sdk.NewTestMsg()}
// create a non-SDK Ethereum transaction // create a non-SDK Ethereum transaction
emintTx := NewTransaction( emintTx := NewTransaction(
@ -284,43 +151,20 @@ func TestTxDecoder(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, emintTx, tx) require.Equal(t, emintTx, tx)
// create embedded transaction and encode // create a SDK (auth.StdTx) transaction and encode
etx := newTestEmbeddedTx( stdTx := newTestStdTx(
testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1}, testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
[]int64{0}, []int64{0}, newStdFee(), []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 // require the transaction to properly decode into a Transaction
txBytes = testCodec.MustMarshalBinary(emintTx) txBytes = testCodec.MustMarshalBinary(stdTx)
tx, err = txDecoder(txBytes) tx, err = txDecoder(txBytes)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedEtx, tx) require.Equal(t, stdTx, tx)
// require the decoding to fail when no transaction bytes are given // require the decoding to fail when no transaction bytes are given
tx, err = txDecoder([]byte{}) tx, err = txDecoder([]byte{})
require.Error(t, err) require.Error(t, err)
require.Nil(t, tx) 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)
} }

View File

@ -2,8 +2,11 @@ package types
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/sha256"
"fmt" "fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto" ethcrypto "github.com/ethereum/go-ethereum/crypto"
@ -39,3 +42,11 @@ func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error {
return nil 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[:]
}

View File

@ -12,12 +12,8 @@ import (
func TestValidateSigner(t *testing.T) { func TestValidateSigner(t *testing.T) {
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
// create message signing structure // create message signing structure and bytes
signEtx := EmbeddedTxSign{testChainID.String(), 0, 0, msgs, newStdFee()} signBytes := GetStdTxSignBytes(testChainID.String(), 0, 0, newStdFee(), msgs, "")
// create signing bytes and sign
signBytes, err := signEtx.Bytes()
require.NoError(t, err)
// require signing not to fail // require signing not to fail
sig, err := ethcrypto.Sign(signBytes, testPrivKey1) sig, err := ethcrypto.Sign(signBytes, testPrivKey1)

View File

@ -18,5 +18,4 @@ func RegisterWire(codec *wire.Codec) {
codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil) codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil)
codec.RegisterConcrete(TxData{}, "types/TxData", nil) codec.RegisterConcrete(TxData{}, "types/TxData", nil)
codec.RegisterConcrete(Transaction{}, "types/Transaction", nil) codec.RegisterConcrete(Transaction{}, "types/Transaction", nil)
codec.RegisterConcrete(EmbeddedTx{}, "types/EmbeddedTx", nil)
} }