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]]
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",

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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())
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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[:]
}

View File

@ -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)

View File

@ -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)
}