diff --git a/Gopkg.lock b/Gopkg.lock index c40e628a..4cbe6077 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,19 +3,19 @@ [[projects]] branch = "master" - digest = "1:fcdf62d2d7e43c2565d6f8707ab4eae54dac702ed4bafb194b85139f0508929f" + digest = "1:79b02529d2120af44eaad5f7fee9ff7e003739d469e2c2767008b797d290e9fd" name = "github.com/aristanetworks/goarista" packages = ["monotime"] pruneopts = "T" - revision = "b2d71c282dc706f4b4f6c15b65810e1202ecd53f" + revision = "18b896026201d8e1758468d9c5d5a558c69c5e9b" [[projects]] branch = "master" - digest = "1:d4d66abd43dbb9b5f5e6a176c5ed279c289f8db734904c047d95113a04aa2e60" + digest = "1:8ad24ea05e770b745b5c286b4f94cf73d5be87005680e36b2d0dd1de0a2f9fbf" name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "T" - revision = "cf05f92c3f815bbd5091ed6c73eff51f7b1945e8" + revision = "d81d8877b8f327112e94e814937143a71d1692a7" [[projects]] digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533" @@ -25,7 +25,7 @@ revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] - digest = "1:36773b598dec105de46a87978ae14e64c8d2c45aa556b8e0ddfc62d6abc7c47e" + digest = "1:bc28e755cf6a9fd8e65497514d20c4907973e7a6a6409d30ead3fd37bfeb19a9" name = "github.com/cosmos/cosmos-sdk" packages = [ "baseapp", @@ -36,16 +36,16 @@ "x/auth", ] pruneopts = "T" - revision = "23e3d5ac12145c02fcb4b4767d7dfccad782aee5" - version = "v0.23.1" + revision = "1c38c70468ec721e3a555ba2f3bf5f9da31f0cc9" + version = "v0.24.2" [[projects]] - digest = "1:52f195ad0e20a92d8604c1ba3cd246c61644c03eaa454b5acd41be89841e0d10" + digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "T" - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] branch = "master" @@ -224,12 +224,12 @@ version = "v0.0.3" [[projects]] - digest = "1:6de2f73eb31e80d74f84ce1c861e4c0c8f00ca5fb41a25901f987e63a0647c28" + digest = "1:9ba911fe3884995431690e7eb180cf848da0d637ba5f61711783b795d031793f" name = "github.com/spf13/pflag" packages = ["."] pruneopts = "T" - revision = "583c0c0531f06d5278b7d917446061adc344b5cd" - version = "v1.0.1" + revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" + version = "v1.0.2" [[projects]] digest = "1:e95496462101745805bd4e041a5b841e108c7cf761264d53648246308de2761e" @@ -245,7 +245,7 @@ [[projects]] branch = "master" - digest = "1:7d44c4d11eb65cfdc78c76040f37ef305b16474c019c98a8a7cf188fece2d574" + digest = "1:ee395d0d8c1719b5a1407f34af93953b4763bacb19a8961aba5b6d312824da41" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -262,7 +262,7 @@ "leveldb/util", ] pruneopts = "T" - revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" + revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd" [[projects]] branch = "master" @@ -276,14 +276,6 @@ pruneopts = "T" revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" -[[projects]] - branch = "master" - digest = "1:3a6bdd02e7f2585c860e368467c5989310740af6206a1ada85cfa19c712e5afd" - name = "github.com/tendermint/ethermint" - packages = ["version"] - pruneopts = "T" - revision = "c1e6ebf80a6cc9119bc178faee18ef13490d707a" - [[projects]] digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245" name = "github.com/tendermint/go-amino" @@ -301,7 +293,7 @@ version = "v0.9.2" [[projects]] - digest = "1:9f6704ae2aedbadf616e5850375c504909d46b6ea57d4679de2b7cbc715f08e1" + digest = "1:5a60cb048b401c0263c227baf8778ecaf038be531707057607949540486874ef" name = "github.com/tendermint/tendermint" packages = [ "abci/server", @@ -321,16 +313,16 @@ "types", ] pruneopts = "T" - revision = "d542d2c3945116697f60451e6a407082c41c3cc9" - version = "v0.22.8" + revision = "81df19e68ab1519399fccf0cab81cb75bf9d782e" + version = "v0.23.1-rc0" [[projects]] branch = "master" - digest = "1:2cbe8758697d867fcebf73bcc69dff8e8abaa7fd65e5704e0744e522ccff4e6a" + digest = "1:da29cbeb9d244918393b37243c008ab7128688fb017c966aaf876587c010bcdd" name = "golang.org/x/crypto" packages = ["ripemd160"] pruneopts = "T" - revision = "f027049dab0ad238e394a753dba2d14753473a04" + revision = "614d502a4dac94afa3a6ce146bd1736da82514c6" [[projects]] digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7" @@ -372,11 +364,12 @@ version = "v0.3.0" [[projects]] - digest = "1:8cfa91d1b7f6b66fa9b1a738a4bc1325837b861e63fb9a2919931d68871bb770" + branch = "master" + digest = "1:960f1fa3f12667fe595c15c12523718ed8b1b5428c83d70da54bb014da9a4c1a" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "T" - revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" + revision = "c66870c02cf823ceb633bcd05be3c7cda29976f4" [[projects]] digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770" @@ -466,7 +459,6 @@ "github.com/pkg/errors", "github.com/stretchr/testify/require", "github.com/stretchr/testify/suite", - "github.com/tendermint/ethermint/version", "github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/log", diff --git a/Gopkg.toml b/Gopkg.toml index 4a6a8e23..77ce1e80 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -4,7 +4,7 @@ [[constraint]] name = "github.com/cosmos/cosmos-sdk" - version = "=0.23.1" + version = "=0.24.2" [[constraint]] name = "github.com/hashicorp/golang-lru" @@ -15,12 +15,12 @@ version = "~0.0.1" [[override]] - name = "google.golang.org/genproto" - revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" + name = "github.com/tendermint/iavl" + version = "=v0.9.2" [[override]] name = "github.com/tendermint/tendermint" - version = "=v0.22.8" + version = "=v0.23.1-rc0" [[constraint]] name = "github.com/stretchr/testify" diff --git a/app/ethermint.go b/app/ethermint.go index 575452eb..e98536f8 100644 --- a/app/ethermint.go +++ b/app/ethermint.go @@ -9,7 +9,6 @@ import ( "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" @@ -43,20 +42,17 @@ type ( // NewEthermintApp returns a reference to a new initialized Ethermint // application. -func NewEthermintApp( - logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, - sdkAddr ethcmn.Address, opts ...Options, +func NewEthermintApp(logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, opts ...Options, ) *EthermintApp { codec := CreateCodec() app := &EthermintApp{ - BaseApp: bam.NewBaseApp(appName, codec, logger, db), + BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec)), 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) diff --git a/docs/spec/transactions/README.md b/docs/spec/transactions/README.md index 2121cee4..040d00bd 100644 --- a/docs/spec/transactions/README.md +++ b/docs/spec/transactions/README.md @@ -6,34 +6,20 @@ 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 `Transaction.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. +the Cosmos hub. We attempt to achieve this by mimicking [Geth's](https://github.com/ethereum/go-ethereum) `Transaction` structure to handle +Ethereum transactions and utilizing the SDK's `auth.StdTx` for Cosmos +transactions. Both of these structures are registered with an [Amino](https://github.com/tendermint/go-amino) codec, so the `TxDecoder` that in invoked +during the `BaseApp#runTx`, will be able to decode raw transaction bytes into the +appropriate transaction type which will then be passed onto handlers downstream. -For Cosmos routed transactions, the `Transaction.Payload` will contain an -embedded encoded type: `EmbeddedTx`. This structure is analogous to the Cosmos -SDK `sdk.StdTx`. If a client wishes to send an `EmbeddedTx`, it must first encode -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. +__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). ## Transactions & Messages 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` 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 no distinction between transactions and messages. -The `EmbeddedTx`, being analogous to the Cosmos SDK `sdk.StdTx`, implements the -Cosmos SDK `sdk.Tx` interface. - ## 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 `EmbeddedTx` Cosmos routed transactions. Signatures over the -`Transaction` type are identical to Ethereum. However, the `EmbeddedTx` contains +signers for `auth.StdTx` Cosmos routed transactions. Signatures over the +`Transaction` type are identical to Ethereum. However, the `auth.StdTx` contains a canonical signature structure that contains the signature itself and other -information such as an account's sequence number. The sequence number is expected -to increment every time a message is signed by a given account. This, in addition -to the chain ID, prevents "replay attacks", where the same message could be -executed over and over again. +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 `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 -the `EmbeddedTx` is responsible for paying the fees. +the `auth.StdTx` is responsible for paying the fees. ## Gas & Fees diff --git a/handlers/ante.go b/handlers/ante.go index 09ad13c1..a1b69d24 100644 --- a/handlers/ante.go +++ b/handlers/ante.go @@ -26,11 +26,10 @@ 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. +// 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(am auth.AccountMapper) sdk.AnteHandler { return func(sdkCtx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { var ( @@ -42,7 +41,7 @@ func AnteHandler(am auth.AccountMapper) sdk.AnteHandler { case types.Transaction: gasLimit = int64(tx.Data.GasLimit) handler = handleEthTx - case types.EmbeddedTx: + case auth.StdTx: gasLimit = tx.Fee.Gas handler = handleEmbeddedTx 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 // 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) + stdTx, ok := tx.(auth.StdTx) if !ok { 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 } - signerAddrs := etx.GetRequiredSigners() + signerAddrs := stdTx.GetSigners() signerAccs := make([]auth.Account, len(signerAddrs)) // validate signatures - for i, sig := range etx.Signatures { + for i, sig := range stdTx.Signatures { 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 { 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) - 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. -func validateEmbeddedTxBasic(etx types.EmbeddedTx) (err sdk.Error) { - sigs := etx.Signatures +func validateStdTxBasic(stdTx auth.StdTx) (err sdk.Error) { + sigs := stdTx.Signatures if len(sigs) == 0 { return sdk.ErrUnauthorized("transaction missing signatures") } - signerAddrs := etx.GetRequiredSigners() + signerAddrs := stdTx.GetSigners() if len(sigs) != len(signerAddrs) { return sdk.ErrUnauthorized("invalid number of transaction signers") } @@ -156,8 +155,8 @@ func validateEmbeddedTxBasic(etx types.EmbeddedTx) (err sdk.Error) { } func validateSignature( - sdkCtx sdk.Context, etx types.EmbeddedTx, signer ethcmn.Address, - sig []byte, am auth.AccountMapper, + sdkCtx sdk.Context, stdTx auth.StdTx, signer ethcmn.Address, + sig auth.StdSignature, am auth.AccountMapper, ) (acc auth.Account, sdkErr sdk.Error) { chainID := sdkCtx.ChainID() @@ -167,28 +166,29 @@ func validateSignature( 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, + accNum := acc.GetAccountNumber() + if accNum != sig.AccountNumber { + return nil, sdk.ErrInvalidSequence( + fmt.Sprintf("invalid account number; got %d, expected %d", sig.AccountNumber, accNum)) } - 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 { return nil, sdk.ErrInternal(err.Error()) } - signBytes, err := signEtx.Bytes() - 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 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()) } diff --git a/types/tx.go b/types/tx.go index a7043d63..d377779b 100644 --- a/types/tx.go +++ b/types/tx.go @@ -1,17 +1,13 @@ 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" @@ -26,13 +22,9 @@ const ( 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 + // 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. // @@ -47,9 +39,7 @@ type ( 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 defines internal Ethereum transaction information TxData struct { AccountNonce uint64 `json:"nonce"` 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 // Transaction is created with that signature. If setting the signature fails, // a panic will be triggered. +// +// TODO: To be removed in #470 func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction { gethTx := ethtypes.NewTransaction( 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 } -// 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 { +// attempts to decode them into a valid sdk.Tx. +func TxDecoder(codec *wire.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { - var tx = Transaction{} - if len(txBytes) == 0 { return nil, sdk.ErrTxDecode("txBytes are empty") } + var tx sdk.Tx + // The given codec should have all the appropriate message types // registered. 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()) } - // 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 } } diff --git a/types/tx_test.go b/types/tx_test.go index b186f7b9..7a75caa9 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -3,14 +3,12 @@ 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" ) @@ -23,64 +21,42 @@ var ( testAddr1 = PrivKeyToEthAddress(testPrivKey1) testAddr2 = PrivKeyToEthAddress(testPrivKey2) - - testSDKAddress = GenerateEthAddress() ) func newTestCodec() *wire.Codec { codec := wire.NewCodec() RegisterWire(codec) + codec.RegisterConcrete(auth.StdTx{}, "test/StdTx", nil) codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) + wire.RegisterCrypto(codec) return codec } 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, accNums []int64, seqs []int64, fee auth.StdFee, ) sdk.Tx { - sigs := make([][]byte, len(pKeys)) + sigs := make([]auth.StdSignature, 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) - } + signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], newStdFee(), msgs, "") sig, err := ethcrypto.Sign(signBytes, priv) if err != nil { panic(err) } - sigs[i] = sig + sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} } - 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 + return auth.NewStdTx(msgs, fee, sigs, "") } 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 } -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, @@ -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) { ethTxs := newTestEthTxs( testChainID, @@ -246,7 +124,7 @@ func TestTransactionGetMsgs(t *testing.T) { require.Equal(t, ethTxs[0], msgs[0]) expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} - etx := newTestEmbeddedTx( + etx := newTestStdTx( testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1}, []int64{0}, []int64{0}, newStdFee(), ) @@ -256,21 +134,10 @@ func TestTransactionGetMsgs(t *testing.T) { 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()))} + txDecoder := TxDecoder(testCodec) + msgs := []sdk.Msg{sdk.NewTestMsg()} // create a non-SDK Ethereum transaction emintTx := NewTransaction( @@ -284,43 +151,20 @@ func TestTxDecoder(t *testing.T) { require.NoError(t, err) require.Equal(t, emintTx, tx) - // create embedded transaction and encode - etx := newTestEmbeddedTx( + // create a SDK (auth.StdTx) transaction and encode + stdTx := newTestStdTx( 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) + txBytes = testCodec.MustMarshalBinary(stdTx) tx, err = txDecoder(txBytes) 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 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) } diff --git a/types/utils.go b/types/utils.go index 01f8261d..9c4f5328 100644 --- a/types/utils.go +++ b/types/utils.go @@ -2,8 +2,11 @@ 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" @@ -39,3 +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[:] +} diff --git a/types/utils_test.go b/types/utils_test.go index cb12c841..39c9e291 100644 --- a/types/utils_test.go +++ b/types/utils_test.go @@ -12,12 +12,8 @@ import ( 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) + // create message signing structure and bytes + signBytes := GetStdTxSignBytes(testChainID.String(), 0, 0, newStdFee(), msgs, "") // require signing not to fail sig, err := ethcrypto.Sign(signBytes, testPrivKey1) diff --git a/types/wire.go b/types/wire.go index 5414fcdc..b596a3db 100644 --- a/types/wire.go +++ b/types/wire.go @@ -18,5 +18,4 @@ func RegisterWire(codec *wire.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) }