Merge pull request #478 from cosmos/bez/477-re-intro-embedded-tx
R4R: Reintroduce Embedded Transactions
This commit is contained in:
commit
ec4c4136b1
@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/cosmos/ethermint/handlers"
|
"github.com/cosmos/ethermint/handlers"
|
||||||
"github.com/cosmos/ethermint/types"
|
"github.com/cosmos/ethermint/types"
|
||||||
|
|
||||||
ethparams "github.com/ethereum/go-ethereum/params"
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
tmcmn "github.com/tendermint/tendermint/libs/common"
|
tmcmn "github.com/tendermint/tendermint/libs/common"
|
||||||
@ -58,12 +58,12 @@ 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, baseAppOptions ...func(*bam.BaseApp),
|
logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address, baseAppOpts ...func(*bam.BaseApp),
|
||||||
) *EthermintApp {
|
) *EthermintApp {
|
||||||
|
|
||||||
codec := CreateCodec()
|
codec := CreateCodec()
|
||||||
app := &EthermintApp{
|
app := &EthermintApp{
|
||||||
BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec), baseAppOptions...),
|
BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec, sdkAddr), baseAppOpts...),
|
||||||
codec: codec,
|
codec: codec,
|
||||||
accountKey: sdk.NewKVStoreKey("acc"),
|
accountKey: sdk.NewKVStoreKey("acc"),
|
||||||
mainKey: sdk.NewKVStoreKey("main"),
|
mainKey: sdk.NewKVStoreKey("main"),
|
||||||
@ -177,7 +177,7 @@ func (app *EthermintApp) initChainer(ctx sdk.Context, req abci.RequestInitChain)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateCodec creates a new amino wire codec and registers all the necessary
|
// CreateCodec creates a new amino wire codec and registers all the necessary
|
||||||
// structures and interfaces needed for the application.
|
// concrete types and interfaces needed for the application.
|
||||||
func CreateCodec() *wire.Codec {
|
func CreateCodec() *wire.Codec {
|
||||||
codec := wire.NewCodec()
|
codec := wire.NewCodec()
|
||||||
|
|
||||||
|
@ -6,15 +6,30 @@ 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 to handle
|
the Cosmos hub. We attempt to achieve this by mimicking [Geth's](https://github.com/ethereum/go-ethereum) `Transaction` structure and utilizing
|
||||||
Ethereum transactions and utilizing the SDK's `auth.StdTx` for Cosmos
|
the `Payload` as the potential encoding of a Cosmos-routed transaction. What
|
||||||
transactions. Both of these structures are registered with an [Amino](https://github.com/tendermint/go-amino) codec, so the `TxDecoder` that in invoked
|
designates this encoding, and ultimately routing, is the `Recipient` address --
|
||||||
during the `BaseApp#runTx`, will be able to decode raw transaction bytes into the
|
if this address matches some global unique predefined and configured address,
|
||||||
appropriate transaction type which will then be passed onto handlers downstream.
|
we regard it as a transaction meant for Cosmos, otherwise, the transaction is a
|
||||||
|
pure Ethereum transaction and will be executed in the EVM.
|
||||||
|
|
||||||
|
For Cosmos routed transactions, the `Transaction.Payload` will contain an [Amino](https://github.com/tendermint/go-amino) encoded embedded transaction that must
|
||||||
|
implement the `sdk.Tx` interface. Note, the embedding (outer) `Transaction` is
|
||||||
|
still RLP encoded in order to preserve compatibility with existing tooling. In
|
||||||
|
addition, at launch, Ethermint will only support the `auth.StdTx` embedded Cosmos
|
||||||
|
transaction type.
|
||||||
|
|
||||||
|
Being that Ethermint implements the Tendermint ABCI application interface, as
|
||||||
|
transactions are consumed, they are passed through a series of handlers. Once such
|
||||||
|
handler, `runTx`, is responsible for invoking the `TxDecoder` which performs the
|
||||||
|
business logic of properly deserializing raw transaction bytes into either an
|
||||||
|
Ethereum transaction or a Cosmos transaction.
|
||||||
|
|
||||||
__Note__: Our goal is to utilize Geth as a library, at least as much as possible,
|
__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
|
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).
|
them will keep in line with Ethereum (e.g. signature algorithms and gas/fees).
|
||||||
|
In addition, we aim to have existing tooling and frameworks in the Ethereum
|
||||||
|
ecosystem have 100% compatibility with creating transactions in Ethermint.
|
||||||
|
|
||||||
## Transactions & Messages
|
## Transactions & Messages
|
||||||
|
|
||||||
@ -33,16 +48,16 @@ no distinction between transactions and messages.
|
|||||||
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 `auth.StdTx` Cosmos routed transactions. Signatures over the
|
signers for embedded Cosmos routed transactions. Signatures over the
|
||||||
`Transaction` type are identical to Ethereum. However, the `auth.StdTx` contains
|
`Transaction` type are identical to Ethereum. However, the embedded transaction 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. This, in addition to the chainID,
|
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
|
helps prevent "replay attacks", where the same message could be executed over and
|
||||||
over again.
|
over again.
|
||||||
|
|
||||||
An `auth.StdTx` list of signatures must much the unique list of addresses returned
|
An embedded transaction's list of signatures must much the unique list of addresses
|
||||||
by each message's `GetSigners` call. In addition, the address of first signer of
|
returned by each message's `GetSigners` call. In addition, the address of first
|
||||||
the `auth.StdTx` is responsible for paying the fees.
|
signer of the embedded transaction is responsible for paying the fees.
|
||||||
|
|
||||||
## Gas & Fees
|
## Gas & Fees
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/cosmos/ethermint/types"
|
"github.com/cosmos/ethermint/types"
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -39,7 +37,7 @@ func AnteHandler(am auth.AccountMapper, _ auth.FeeCollectionKeeper) sdk.AnteHand
|
|||||||
|
|
||||||
switch tx := tx.(type) {
|
switch tx := tx.(type) {
|
||||||
case types.Transaction:
|
case types.Transaction:
|
||||||
gasLimit = int64(tx.Data.GasLimit)
|
gasLimit = int64(tx.Data().GasLimit)
|
||||||
handler = handleEthTx
|
handler = handleEthTx
|
||||||
case auth.StdTx:
|
case auth.StdTx:
|
||||||
gasLimit = tx.Fee.Gas
|
gasLimit = tx.Fee.Gas
|
||||||
@ -91,16 +89,28 @@ func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk.Cont
|
|||||||
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true
|
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate signature
|
sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante: verify Ethereum signature")
|
||||||
gethTx := ethTx.ConvertTx(chainID)
|
|
||||||
signer := ethtypes.NewEIP155Signer(chainID)
|
|
||||||
|
|
||||||
_, err := signer.Sender(&gethTx)
|
addr, err := ethTx.VerifySig(chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true
|
return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data.GasLimit)}, false
|
acc := am.GetAccount(sdkCtx, addr.Bytes())
|
||||||
|
|
||||||
|
// validate the account nonce (referred to as sequence in the AccountMapper)
|
||||||
|
seq := acc.GetSequence()
|
||||||
|
if ethTx.Data().AccountNonce != uint64(seq) {
|
||||||
|
return sdkCtx, sdk.ErrInvalidSequence(fmt.Sprintf("invalid account nonce; expected: %d", seq)).Result(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
err = acc.SetSequence(seq + 1)
|
||||||
|
if err != nil {
|
||||||
|
return sdkCtx, sdk.ErrInternal(err.Error()).Result(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
am.SetAccount(sdkCtx, acc)
|
||||||
|
return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data().GasLimit)}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleEmbeddedTx implements an ante handler for an SDK transaction. It
|
// handleEmbeddedTx implements an ante handler for an SDK transaction. It
|
||||||
@ -122,15 +132,15 @@ func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk
|
|||||||
for i, sig := range stdTx.Signatures {
|
for i, sig := range stdTx.Signatures {
|
||||||
signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes())
|
signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes())
|
||||||
|
|
||||||
signerAcc, err := validateSignature(sdkCtx, stdTx, signer, sig, am)
|
acc, 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(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fees!
|
// TODO: Fees!
|
||||||
|
|
||||||
am.SetAccount(sdkCtx, signerAcc)
|
am.SetAccount(sdkCtx, acc)
|
||||||
signerAccs[i] = signerAcc
|
signerAccs[i] = acc
|
||||||
}
|
}
|
||||||
|
|
||||||
newCtx := auth.WithSigners(sdkCtx, signerAccs)
|
newCtx := auth.WithSigners(sdkCtx, signerAccs)
|
||||||
|
121
types/test_common.go
Normal file
121
types/test_common.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// nolint
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TestSDKAddr = GenerateEthAddress()
|
||||||
|
TestChainID = big.NewInt(3)
|
||||||
|
|
||||||
|
TestPrivKey1, _ = ethcrypto.GenerateKey()
|
||||||
|
TestPrivKey2, _ = ethcrypto.GenerateKey()
|
||||||
|
|
||||||
|
TestAddr1 = PrivKeyToEthAddress(TestPrivKey1)
|
||||||
|
TestAddr2 = PrivKeyToEthAddress(TestPrivKey2)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTestCodec() *wire.Codec {
|
||||||
|
codec := wire.NewCodec()
|
||||||
|
|
||||||
|
RegisterWire(codec)
|
||||||
|
auth.RegisterWire(codec)
|
||||||
|
wire.RegisterCrypto(codec)
|
||||||
|
codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
|
||||||
|
|
||||||
|
return codec
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestStdFee() auth.StdFee {
|
||||||
|
return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestStdTx(
|
||||||
|
chainID *big.Int, msgs []sdk.Msg, accNums []int64, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee,
|
||||||
|
) sdk.Tx {
|
||||||
|
|
||||||
|
sigs := make([]auth.StdSignature, len(pKeys))
|
||||||
|
|
||||||
|
for i, priv := range pKeys {
|
||||||
|
signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], NewTestStdFee(), msgs, "")
|
||||||
|
|
||||||
|
sig, err := ethcrypto.Sign(signBytes, priv)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]}
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.NewStdTx(msgs, fee, sigs, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestGethTxs(
|
||||||
|
chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey,
|
||||||
|
) []*ethtypes.Transaction {
|
||||||
|
|
||||||
|
txs := make([]*ethtypes.Transaction, len(pKeys))
|
||||||
|
|
||||||
|
for i, privKey := range pKeys {
|
||||||
|
ethTx := ethtypes.NewTransaction(
|
||||||
|
uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{},
|
||||||
|
)
|
||||||
|
|
||||||
|
signer := ethtypes.NewEIP155Signer(chainID)
|
||||||
|
|
||||||
|
ethTx, err := ethtypes.SignTx(ethTx, signer, privKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txs[i] = ethTx
|
||||||
|
}
|
||||||
|
|
||||||
|
return txs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestEthTxs(
|
||||||
|
chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey,
|
||||||
|
) []*Transaction {
|
||||||
|
|
||||||
|
txs := make([]*Transaction, len(pKeys))
|
||||||
|
|
||||||
|
for i, privKey := range pKeys {
|
||||||
|
ethTx := NewTransaction(
|
||||||
|
uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{},
|
||||||
|
)
|
||||||
|
|
||||||
|
ethTx.Sign(chainID, privKey)
|
||||||
|
txs[i] = ethTx
|
||||||
|
}
|
||||||
|
|
||||||
|
return txs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestSDKTxs(
|
||||||
|
codec *wire.Codec, chainID *big.Int, to ethcmn.Address, msgs []sdk.Msg,
|
||||||
|
accNums []int64, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee,
|
||||||
|
) []*Transaction {
|
||||||
|
|
||||||
|
txs := make([]*Transaction, len(pKeys))
|
||||||
|
stdTx := NewTestStdTx(chainID, msgs, accNums, seqs, pKeys, fee)
|
||||||
|
payload := codec.MustMarshalBinary(stdTx)
|
||||||
|
|
||||||
|
for i, privKey := range pKeys {
|
||||||
|
ethTx := NewTransaction(uint64(seqs[i]), to, big.NewInt(10), 1000, big.NewInt(100), payload)
|
||||||
|
|
||||||
|
ethTx.Sign(chainID, privKey)
|
||||||
|
txs[i] = ethTx
|
||||||
|
}
|
||||||
|
|
||||||
|
return txs
|
||||||
|
}
|
339
types/tx.go
339
types/tx.go
@ -1,8 +1,10 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@ -13,17 +15,20 @@ import (
|
|||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
|
ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ sdk.Tx = (*Transaction)(nil)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TypeTxEthereum reflects an Ethereum Transaction type.
|
// TypeTxEthereum reflects an Ethereum Transaction type.
|
||||||
TypeTxEthereum = "Ethereum"
|
TypeTxEthereum = "Ethereum"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Ethereum transaction
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var _ sdk.Tx = (*Transaction)(nil)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Transaction implements the Ethereum transaction structure as an exact
|
// Transaction implements the Ethereum transaction structure as an exact
|
||||||
// replica. It implements the Cosmos sdk.Tx interface. Due to the private
|
// replica. It implements the Cosmos sdk.Tx interface. Due to the private
|
||||||
@ -33,7 +38,7 @@ type (
|
|||||||
// Note: The transaction also implements the sdk.Msg interface to perform
|
// Note: The transaction also implements the sdk.Msg interface to perform
|
||||||
// basic validation that is done in the BaseApp.
|
// basic validation that is done in the BaseApp.
|
||||||
Transaction struct {
|
Transaction struct {
|
||||||
Data TxData
|
data TxData
|
||||||
|
|
||||||
// caches
|
// caches
|
||||||
hash atomic.Value
|
hash atomic.Value
|
||||||
@ -41,96 +46,136 @@ type (
|
|||||||
from atomic.Value
|
from atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxData defines internal Ethereum transaction information
|
// TxData implements the Ethereum transaction data structure as an exact
|
||||||
|
// copy. It is used solely as intended in Ethereum abiding by the protocol
|
||||||
|
// except for the payload field which may embed a Cosmos SDK transaction.
|
||||||
TxData struct {
|
TxData struct {
|
||||||
AccountNonce uint64 `json:"nonce"`
|
AccountNonce uint64 `json:"nonce"`
|
||||||
Price sdk.Int `json:"gasPrice"`
|
Price *big.Int `json:"gasPrice"`
|
||||||
GasLimit uint64 `json:"gas"`
|
GasLimit uint64 `json:"gas"`
|
||||||
Recipient *ethcmn.Address `json:"to"` // nil means contract creation
|
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
|
||||||
Amount sdk.Int `json:"value"`
|
Amount *big.Int `json:"value"`
|
||||||
Payload []byte `json:"input"`
|
Payload []byte `json:"input"`
|
||||||
Signature *EthSignature `json:"signature"`
|
|
||||||
|
// signature values
|
||||||
|
V *big.Int `json:"v"`
|
||||||
|
R *big.Int `json:"r"`
|
||||||
|
S *big.Int `json:"s"`
|
||||||
|
|
||||||
// hash is only used when marshaling to JSON
|
// hash is only used when marshaling to JSON
|
||||||
Hash *ethcmn.Hash `json:"hash"`
|
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EthSignature reflects an Ethereum signature. We wrap this in a structure
|
// sigCache is used to cache the derived sender and contains the signer used
|
||||||
// to support Amino serialization of transactions.
|
// to derive it.
|
||||||
EthSignature struct {
|
sigCache struct {
|
||||||
v, r, s *big.Int
|
signer ethtypes.Signer
|
||||||
|
from ethcmn.Address
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewEthSignature returns a new instantiated Ethereum signature.
|
// NewTransaction returns a reference to a new Ethereum transaction.
|
||||||
func NewEthSignature(v, r, s *big.Int) *EthSignature {
|
|
||||||
return &EthSignature{v, r, s}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *EthSignature) sanitize() {
|
|
||||||
if es.v == nil {
|
|
||||||
es.v = new(big.Int)
|
|
||||||
}
|
|
||||||
if es.r == nil {
|
|
||||||
es.r = new(big.Int)
|
|
||||||
}
|
|
||||||
if es.s == nil {
|
|
||||||
es.s = new(big.Int)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalAmino defines a custom encoding scheme for a EthSignature.
|
|
||||||
func (es EthSignature) MarshalAmino() ([3]string, error) {
|
|
||||||
es.sanitize()
|
|
||||||
return ethSigMarshalAmino(es)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalAmino defines a custom decoding scheme for a EthSignature.
|
|
||||||
func (es *EthSignature) UnmarshalAmino(raw [3]string) error {
|
|
||||||
es.sanitize()
|
|
||||||
return ethSigUnmarshalAmino(es, raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTransaction mimics ethereum's NewTransaction function. It returns a
|
|
||||||
// reference to a new Ethereum Transaction.
|
|
||||||
func NewTransaction(
|
func NewTransaction(
|
||||||
nonce uint64, to ethcmn.Address, amount sdk.Int,
|
nonce uint64, to ethcmn.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
gasLimit uint64, gasPrice sdk.Int, payload []byte,
|
) *Transaction {
|
||||||
) Transaction {
|
|
||||||
|
return newTransaction(nonce, &to, amount, gasLimit, gasPrice, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContractCreation returns a reference to a new Ethereum transaction
|
||||||
|
// designated for contract creation.
|
||||||
|
func NewContractCreation(
|
||||||
|
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
|
) *Transaction {
|
||||||
|
|
||||||
|
return newTransaction(nonce, nil, amount, gasLimit, gasPrice, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransaction(
|
||||||
|
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
||||||
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
|
) *Transaction {
|
||||||
|
|
||||||
if len(payload) > 0 {
|
if len(payload) > 0 {
|
||||||
payload = ethcmn.CopyBytes(payload)
|
payload = ethcmn.CopyBytes(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
txData := TxData{
|
txData := TxData{
|
||||||
Recipient: &to,
|
|
||||||
AccountNonce: nonce,
|
AccountNonce: nonce,
|
||||||
|
Recipient: to,
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
GasLimit: gasLimit,
|
GasLimit: gasLimit,
|
||||||
Amount: amount,
|
Amount: new(big.Int),
|
||||||
Price: gasPrice,
|
Price: new(big.Int),
|
||||||
Signature: NewEthSignature(new(big.Int), new(big.Int), new(big.Int)),
|
V: new(big.Int),
|
||||||
|
R: new(big.Int),
|
||||||
|
S: new(big.Int),
|
||||||
}
|
}
|
||||||
|
|
||||||
return Transaction{Data: txData}
|
if amount != nil {
|
||||||
|
txData.Amount.Set(amount)
|
||||||
|
}
|
||||||
|
if gasPrice != nil {
|
||||||
|
txData.Price.Set(gasPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Transaction{data: txData}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data returns the Transaction's data.
|
||||||
|
func (tx Transaction) Data() TxData {
|
||||||
|
return tx.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeRLP implements the rlp.Encoder interface.
|
||||||
|
func (tx *Transaction) EncodeRLP(w io.Writer) error {
|
||||||
|
return rlp.Encode(w, &tx.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRLP implements the rlp.Decoder interface.
|
||||||
|
func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
|
||||||
|
_, size, _ := s.Kind()
|
||||||
|
err := s.Decode(&tx.data)
|
||||||
|
if err == nil {
|
||||||
|
tx.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash hashes the RLP encoding of a transaction.
|
||||||
|
func (tx *Transaction) Hash() ethcmn.Hash {
|
||||||
|
if hash := tx.hash.Load(); hash != nil {
|
||||||
|
return hash.(ethcmn.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := rlpHash(tx)
|
||||||
|
tx.hash.Store(v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigHash returns the RLP hash of a transaction with a given chainID used for
|
||||||
|
// signing.
|
||||||
|
func (tx Transaction) SigHash(chainID *big.Int) ethcmn.Hash {
|
||||||
|
return rlpHash([]interface{}{
|
||||||
|
tx.data.AccountNonce,
|
||||||
|
tx.data.Price,
|
||||||
|
tx.data.GasLimit,
|
||||||
|
tx.data.Recipient,
|
||||||
|
tx.data.Amount,
|
||||||
|
tx.data.Payload,
|
||||||
|
chainID, uint(0), uint(0),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
|
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
|
||||||
// takes a private key and chainID to sign an Ethereum transaction according to
|
// takes a private key and chainID to sign an Ethereum transaction according to
|
||||||
// EIP155 standard. It mutates the transaction as it populates the V, R, S
|
// EIP155 standard. It mutates the transaction as it populates the V, R, S
|
||||||
// fields of the Transaction's Signature.
|
// fields of the Transaction's Signature.
|
||||||
func (tx *Transaction) Sign(chainID sdk.Int, priv *ecdsa.PrivateKey) {
|
func (tx *Transaction) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
||||||
h := rlpHash([]interface{}{
|
txHash := tx.SigHash(chainID)
|
||||||
tx.Data.AccountNonce,
|
|
||||||
tx.Data.Price.BigInt(),
|
|
||||||
tx.Data.GasLimit,
|
|
||||||
tx.Data.Recipient,
|
|
||||||
tx.Data.Amount.BigInt(),
|
|
||||||
tx.Data.Payload,
|
|
||||||
chainID.BigInt(), uint(0), uint(0),
|
|
||||||
})
|
|
||||||
|
|
||||||
sig, err := ethcrypto.Sign(h[:], priv)
|
sig, err := ethcrypto.Sign(txHash[:], priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -147,13 +192,48 @@ func (tx *Transaction) Sign(chainID sdk.Int, priv *ecdsa.PrivateKey) {
|
|||||||
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
|
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
|
||||||
} else {
|
} else {
|
||||||
v = big.NewInt(int64(sig[64] + 35))
|
v = big.NewInt(int64(sig[64] + 35))
|
||||||
chainIDMul := new(big.Int).Mul(chainID.BigInt(), big.NewInt(2))
|
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
||||||
v.Add(v, chainIDMul)
|
v.Add(v, chainIDMul)
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.Data.Signature.v = v
|
tx.data.V = v
|
||||||
tx.Data.Signature.r = r
|
tx.data.R = r
|
||||||
tx.Data.Signature.s = s
|
tx.data.S = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifySig attempts to verify a Transaction's signature for a given chainID.
|
||||||
|
// A derived address is returned upon success or an error if recovery fails.
|
||||||
|
func (tx Transaction) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
|
||||||
|
signer := ethtypes.NewEIP155Signer(chainID)
|
||||||
|
|
||||||
|
if sc := tx.from.Load(); sc != nil {
|
||||||
|
sigCache := sc.(sigCache)
|
||||||
|
// If the signer used to derive from in a previous
|
||||||
|
// call is not the same as used current, invalidate
|
||||||
|
// the cache.
|
||||||
|
if sigCache.signer.Equal(signer) {
|
||||||
|
return sigCache.from, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not allow recovery for transactions with an unprotected chainID
|
||||||
|
if chainID.Sign() == 0 {
|
||||||
|
return ethcmn.Address{}, errors.New("invalid chainID")
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash := tx.SigHash(chainID)
|
||||||
|
sig := recoverEthSig(tx.data.R, tx.data.S, tx.data.V, chainID)
|
||||||
|
|
||||||
|
pub, err := ethcrypto.Ecrecover(txHash[:], sig)
|
||||||
|
if err != nil {
|
||||||
|
return ethcmn.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr ethcmn.Address
|
||||||
|
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
|
||||||
|
|
||||||
|
tx.from.Store(sigCache{signer: signer, from: addr})
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type implements the sdk.Msg interface. It returns the type of the
|
// Type implements the sdk.Msg interface. It returns the type of the
|
||||||
@ -165,11 +245,11 @@ func (tx Transaction) Type() string {
|
|||||||
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
||||||
// checks of a Transaction. If returns an sdk.Error if validation fails.
|
// checks of a Transaction. If returns an sdk.Error if validation fails.
|
||||||
func (tx Transaction) ValidateBasic() sdk.Error {
|
func (tx Transaction) ValidateBasic() sdk.Error {
|
||||||
if tx.Data.Price.Sign() != 1 {
|
if tx.data.Price.Sign() != 1 {
|
||||||
return ErrInvalidValue(DefaultCodespace, "price must be positive")
|
return ErrInvalidValue(DefaultCodespace, "price must be positive")
|
||||||
}
|
}
|
||||||
|
|
||||||
if tx.Data.Amount.Sign() != 1 {
|
if tx.data.Amount.Sign() != 1 {
|
||||||
return ErrInvalidValue(DefaultCodespace, "amount must be positive")
|
return ErrInvalidValue(DefaultCodespace, "amount must be positive")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,44 +272,57 @@ func (tx Transaction) GetMsgs() []sdk.Msg {
|
|||||||
return []sdk.Msg{tx}
|
return []sdk.Msg{tx}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertTx attempts to converts a Transaction to a new Ethereum transaction
|
// hasEmbeddedTx returns a boolean reflecting if the transaction contains an
|
||||||
// with the signature set. The signature if first recovered and then a new
|
// SDK transaction or not based on the recipient address.
|
||||||
// Transaction is created with that signature. If setting the signature fails,
|
func (tx Transaction) hasEmbeddedTx(addr ethcmn.Address) bool {
|
||||||
// a panic will be triggered.
|
return bytes.Equal(tx.data.Recipient.Bytes(), addr.Bytes())
|
||||||
//
|
|
||||||
// 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(),
|
|
||||||
tx.Data.GasLimit, tx.Data.Price.BigInt(), tx.Data.Payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
sig := recoverEthSig(tx.Data.Signature, chainID)
|
|
||||||
signer := ethtypes.NewEIP155Signer(chainID)
|
|
||||||
|
|
||||||
gethTx, err := gethTx.WithSignature(signer, sig)
|
|
||||||
if err != nil {
|
|
||||||
panic(errors.Wrap(err, "failed to convert transaction with a given signature"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return *gethTx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes,
|
// GetEmbeddedTx returns the embedded SDK transaction from an Ethereum
|
||||||
// attempts to decode them into a valid sdk.Tx.
|
// transaction. It returns an error if decoding the inner transaction fails.
|
||||||
func TxDecoder(codec *wire.Codec) sdk.TxDecoder {
|
//
|
||||||
|
// CONTRACT: The payload field of an Ethereum transaction must contain a valid
|
||||||
|
// encoded SDK transaction.
|
||||||
|
func (tx Transaction) GetEmbeddedTx(codec *wire.Codec) (sdk.Tx, sdk.Error) {
|
||||||
|
var etx sdk.Tx
|
||||||
|
|
||||||
|
err := codec.UnmarshalBinary(tx.data.Payload, &etx)
|
||||||
|
if err != nil {
|
||||||
|
return etx, sdk.ErrTxDecode("failed to decode embedded transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return etx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Utilities
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes and an
|
||||||
|
// SDK address, attempts to decode them into a Transaction or an EmbeddedTx or
|
||||||
|
// returning an error if decoding fails.
|
||||||
|
func TxDecoder(codec *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("transaction bytes are empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
var tx sdk.Tx
|
err := rlp.DecodeBytes(txBytes, &tx)
|
||||||
|
|
||||||
// The given codec should have all the appropriate message types
|
|
||||||
// registered.
|
|
||||||
err := codec.UnmarshalBinary(txBytes, &tx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error())
|
return nil, sdk.ErrTxDecode("failed to decode transaction").TraceSDK(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the transaction is routed as an SDK transaction, decode and return
|
||||||
|
// the embedded SDK transaction.
|
||||||
|
if tx.hasEmbeddedTx(sdkAddress) {
|
||||||
|
etx, err := tx.GetEmbeddedTx(codec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return etx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx, nil
|
return tx, nil
|
||||||
@ -237,20 +330,20 @@ func TxDecoder(codec *wire.Codec) sdk.TxDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// recoverEthSig recovers a signature according to the Ethereum specification.
|
// recoverEthSig recovers a signature according to the Ethereum specification.
|
||||||
func recoverEthSig(es *EthSignature, chainID *big.Int) []byte {
|
func recoverEthSig(R, S, Vb, chainID *big.Int) []byte {
|
||||||
var v byte
|
var v byte
|
||||||
|
|
||||||
r, s := es.r.Bytes(), es.s.Bytes()
|
r, s := R.Bytes(), S.Bytes()
|
||||||
sig := make([]byte, 65)
|
sig := make([]byte, 65)
|
||||||
|
|
||||||
copy(sig[32-len(r):32], r)
|
copy(sig[32-len(r):32], r)
|
||||||
copy(sig[64-len(s):64], s)
|
copy(sig[64-len(s):64], s)
|
||||||
|
|
||||||
if chainID.Sign() == 0 {
|
if chainID.Sign() == 0 {
|
||||||
v = byte(es.v.Uint64() - 27)
|
v = byte(Vb.Uint64() - 27)
|
||||||
} else {
|
} else {
|
||||||
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
||||||
V := new(big.Int).Sub(es.v, chainIDMul)
|
V := new(big.Int).Sub(Vb, chainIDMul)
|
||||||
|
|
||||||
v = byte(V.Uint64() - 35)
|
v = byte(V.Uint64() - 35)
|
||||||
}
|
}
|
||||||
@ -259,43 +352,11 @@ func recoverEthSig(es *EthSignature, chainID *big.Int) []byte {
|
|||||||
return sig
|
return sig
|
||||||
}
|
}
|
||||||
|
|
||||||
func rlpHash(x interface{}) (h ethcmn.Hash) {
|
func rlpHash(x interface{}) (hash ethcmn.Hash) {
|
||||||
hasher := ethsha.NewKeccak256()
|
hasher := ethsha.NewKeccak256()
|
||||||
|
|
||||||
rlp.Encode(hasher, x)
|
rlp.Encode(hasher, x)
|
||||||
hasher.Sum(h[:0])
|
hasher.Sum(hash[:0])
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func ethSigMarshalAmino(es EthSignature) (raw [3]string, err error) {
|
|
||||||
vb, err := es.v.MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
return raw, err
|
|
||||||
}
|
|
||||||
rb, err := es.r.MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
return raw, err
|
|
||||||
}
|
|
||||||
sb, err := es.s.MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
return raw, err
|
|
||||||
}
|
|
||||||
|
|
||||||
raw[0], raw[1], raw[2] = string(vb), string(rb), string(sb)
|
|
||||||
return raw, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ethSigUnmarshalAmino(es *EthSignature, raw [3]string) (err error) {
|
|
||||||
if err = es.v.UnmarshalText([]byte(raw[0])); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = es.r.UnmarshalText([]byte(raw[1])); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = es.s.UnmarshalText([]byte(raw[2])); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
158
types/tx_test.go
158
types/tx_test.go
@ -3,83 +3,57 @@ 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/x/auth"
|
"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"
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func TestTransactionRLPEncode(t *testing.T) {
|
||||||
testChainID = sdk.NewInt(3)
|
txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
|
||||||
|
gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
|
||||||
|
|
||||||
testPrivKey1, _ = ethcrypto.GenerateKey()
|
txRLP, err := rlp.EncodeToBytes(txs[0])
|
||||||
testPrivKey2, _ = ethcrypto.GenerateKey()
|
require.NoError(t, err)
|
||||||
|
|
||||||
testAddr1 = PrivKeyToEthAddress(testPrivKey1)
|
gtxRLP, err := rlp.EncodeToBytes(gtxs[0])
|
||||||
testAddr2 = PrivKeyToEthAddress(testPrivKey2)
|
require.NoError(t, err)
|
||||||
)
|
|
||||||
|
|
||||||
func newTestCodec() *wire.Codec {
|
require.Equal(t, gtxRLP, txRLP)
|
||||||
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 {
|
func TestTransactionRLPDecode(t *testing.T) {
|
||||||
return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150)))
|
txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
|
||||||
}
|
gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
|
||||||
|
|
||||||
func newTestStdTx(
|
txRLP, err := rlp.EncodeToBytes(txs[0])
|
||||||
chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey,
|
require.NoError(t, err)
|
||||||
accNums []int64, seqs []int64, fee auth.StdFee,
|
|
||||||
) sdk.Tx {
|
|
||||||
|
|
||||||
sigs := make([]auth.StdSignature, len(pKeys))
|
gtxRLP, err := rlp.EncodeToBytes(gtxs[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
for i, priv := range pKeys {
|
var (
|
||||||
signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], newStdFee(), msgs, "")
|
decodedTx Transaction
|
||||||
|
decodedGtx ethtypes.Transaction
|
||||||
|
)
|
||||||
|
|
||||||
sig, err := ethcrypto.Sign(signBytes, priv)
|
err = rlp.DecodeBytes(txRLP, &decodedTx)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]}
|
err = rlp.DecodeBytes(gtxRLP, &decodedGtx)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
return auth.NewStdTx(msgs, fee, sigs, "")
|
require.Equal(t, decodedGtx.Hash(), decodedTx.Hash())
|
||||||
}
|
|
||||||
|
|
||||||
func newTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []Transaction {
|
|
||||||
txs := make([]Transaction, len(pKeys))
|
|
||||||
|
|
||||||
for i, priv := range pKeys {
|
|
||||||
emintTx := NewTransaction(
|
|
||||||
uint64(i), addrs[i], sdk.NewInt(10), 100, sdk.NewInt(100), nil,
|
|
||||||
)
|
|
||||||
|
|
||||||
emintTx.Sign(chainID, priv)
|
|
||||||
|
|
||||||
txs[i] = emintTx
|
|
||||||
}
|
|
||||||
|
|
||||||
return txs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidation(t *testing.T) {
|
func TestValidation(t *testing.T) {
|
||||||
ethTxs := newTestEthTxs(
|
ethTxs := NewTestEthTxs(
|
||||||
testChainID,
|
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
|
||||||
[]*ecdsa.PrivateKey{testPrivKey1},
|
|
||||||
[]ethcmn.Address{testAddr1},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@ -89,13 +63,13 @@ func TestValidation(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{ethTxs[0], func(msg sdk.Msg) sdk.Msg { return msg }, false},
|
{ethTxs[0], func(msg sdk.Msg) sdk.Msg { return msg }, false},
|
||||||
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
|
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
|
||||||
tx := msg.(Transaction)
|
tx := msg.(*Transaction)
|
||||||
tx.Data.Price = sdk.NewInt(-1)
|
tx.data.Price = big.NewInt(-1)
|
||||||
return tx
|
return tx
|
||||||
}, true},
|
}, true},
|
||||||
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
|
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
|
||||||
tx := msg.(Transaction)
|
tx := msg.(*Transaction)
|
||||||
tx.Data.Amount = sdk.NewInt(-1)
|
tx.data.Amount = big.NewInt(-1)
|
||||||
return tx
|
return tx
|
||||||
}, true},
|
}, true},
|
||||||
}
|
}
|
||||||
@ -112,59 +86,57 @@ func TestValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransactionGetMsgs(t *testing.T) {
|
func TestTransactionVerifySig(t *testing.T) {
|
||||||
ethTxs := newTestEthTxs(
|
txs := NewTestEthTxs(
|
||||||
testChainID,
|
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
|
||||||
[]*ecdsa.PrivateKey{testPrivKey1},
|
|
||||||
[]ethcmn.Address{testAddr1},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
msgs := ethTxs[0].GetMsgs()
|
addr, err := txs[0].VerifySig(TestChainID)
|
||||||
require.Len(t, msgs, 1)
|
require.NoError(t, err)
|
||||||
require.Equal(t, ethTxs[0], msgs[0])
|
require.Equal(t, TestAddr1, addr)
|
||||||
|
|
||||||
expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
|
addr, err = txs[0].VerifySig(big.NewInt(100))
|
||||||
etx := newTestStdTx(
|
require.Error(t, err)
|
||||||
testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1},
|
require.NotEqual(t, TestAddr1, addr)
|
||||||
[]int64{0}, []int64{0}, newStdFee(),
|
|
||||||
)
|
|
||||||
|
|
||||||
msgs = etx.GetMsgs()
|
|
||||||
require.Len(t, msgs, len(expectedMsgs))
|
|
||||||
require.Equal(t, expectedMsgs, msgs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTxDecoder(t *testing.T) {
|
func TestTxDecoder(t *testing.T) {
|
||||||
testCodec := newTestCodec()
|
testCodec := NewTestCodec()
|
||||||
txDecoder := TxDecoder(testCodec)
|
txDecoder := TxDecoder(testCodec, TestSDKAddr)
|
||||||
msgs := []sdk.Msg{sdk.NewTestMsg()}
|
msgs := []sdk.Msg{sdk.NewTestMsg()}
|
||||||
|
|
||||||
// create a non-SDK Ethereum transaction
|
// create a non-SDK Ethereum transaction
|
||||||
emintTx := NewTransaction(
|
txs := NewTestEthTxs(
|
||||||
uint64(0), testAddr1, sdk.NewInt(10), 100, sdk.NewInt(100), nil,
|
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
|
||||||
)
|
)
|
||||||
emintTx.Sign(testChainID, testPrivKey1)
|
|
||||||
|
txBytes, err := rlp.EncodeToBytes(txs[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// require the transaction to properly decode into a Transaction
|
// require the transaction to properly decode into a Transaction
|
||||||
txBytes := testCodec.MustMarshalBinary(emintTx)
|
decodedTx, err := txDecoder(txBytes)
|
||||||
tx, err := txDecoder(txBytes)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, emintTx, tx)
|
require.IsType(t, Transaction{}, decodedTx)
|
||||||
|
require.Equal(t, txs[0].data, (decodedTx.(Transaction)).data)
|
||||||
|
|
||||||
// create a SDK (auth.StdTx) transaction and encode
|
// create a SDK (auth.StdTx) transaction and encode
|
||||||
stdTx := newTestStdTx(
|
txs = NewTestSDKTxs(
|
||||||
testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
|
testCodec, TestChainID, TestSDKAddr, msgs, []int64{0}, []int64{0},
|
||||||
[]int64{0}, []int64{0}, newStdFee(),
|
[]*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// require the transaction to properly decode into a Transaction
|
txBytes, err = rlp.EncodeToBytes(txs[0])
|
||||||
txBytes = testCodec.MustMarshalBinary(stdTx)
|
|
||||||
tx, err = txDecoder(txBytes)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, stdTx, tx)
|
|
||||||
|
// require the transaction to properly decode into a Transaction
|
||||||
|
stdTx := NewTestStdTx(TestChainID, msgs, []int64{0}, []int64{0}, []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee())
|
||||||
|
decodedTx, err = txDecoder(txBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.IsType(t, auth.StdTx{}, decodedTx)
|
||||||
|
require.Equal(t, stdTx, decodedTx)
|
||||||
|
|
||||||
// require the decoding to fail when no transaction bytes are given
|
// require the decoding to fail when no transaction bytes are given
|
||||||
tx, err = txDecoder([]byte{})
|
decodedTx, err = txDecoder([]byte{})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, tx)
|
require.Nil(t, decodedTx)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error {
|
|||||||
pk, err := ethcrypto.SigToPub(signBytes, sig)
|
pk, err := ethcrypto.SigToPub(signBytes, sig)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "signature verification failed")
|
return errors.Wrap(err, "failed to derive public key from signature")
|
||||||
} else if ethcrypto.PubkeyToAddress(*pk) != signer {
|
} else if ethcrypto.PubkeyToAddress(*pk) != signer {
|
||||||
return fmt.Errorf("invalid signature for signer: %s", signer)
|
return fmt.Errorf("invalid signature for signer: %s", signer)
|
||||||
}
|
}
|
||||||
|
@ -10,23 +10,27 @@ 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 and bytes
|
// create message signing structure and bytes
|
||||||
signBytes := GetStdTxSignBytes(testChainID.String(), 0, 0, newStdFee(), msgs, "")
|
signBytes := GetStdTxSignBytes(TestChainID.String(), 0, 0, NewTestStdFee(), msgs, "")
|
||||||
|
|
||||||
// require signing not to fail
|
// require signing not to fail
|
||||||
sig, err := ethcrypto.Sign(signBytes, testPrivKey1)
|
sig, err := ethcrypto.Sign(signBytes, TestPrivKey1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// require signature to be valid
|
// require signature to be valid
|
||||||
err = ValidateSigner(signBytes, sig, testAddr1)
|
err = ValidateSigner(signBytes, sig, TestAddr1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
sig, err = ethcrypto.Sign(signBytes, testPrivKey2)
|
sig, err = ethcrypto.Sign(signBytes, TestPrivKey2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// require signature to be invalid
|
// require signature to be invalid
|
||||||
err = ValidateSigner(signBytes, sig, testAddr1)
|
err = ValidateSigner(signBytes, sig, TestAddr1)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// require invalid signature bytes return an error
|
||||||
|
err = ValidateSigner([]byte{}, sig, TestAddr2)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,4 @@ func init() {
|
|||||||
func RegisterWire(codec *wire.Codec) {
|
func RegisterWire(codec *wire.Codec) {
|
||||||
sdk.RegisterWire(codec)
|
sdk.RegisterWire(codec)
|
||||||
codec.RegisterConcrete(&Account{}, "types/Account", nil)
|
codec.RegisterConcrete(&Account{}, "types/Account", nil)
|
||||||
codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil)
|
|
||||||
codec.RegisterConcrete(TxData{}, "types/TxData", nil)
|
|
||||||
codec.RegisterConcrete(Transaction{}, "types/Transaction", nil)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user