Revert tx logic to use embedded txs with dedicated addr

This commit is contained in:
Aleksandr Bezobchuk 2018-08-30 17:36:14 -04:00
parent 4f211af49a
commit 5621242668
7 changed files with 419 additions and 254 deletions

View File

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

View File

@ -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())
}
// GetEmbeddedTx returns the embedded SDK transaction from an Ethereum
// transaction. It returns an error if decoding the inner transaction fails.
// //
// TODO: To be removed in #470 // CONTRACT: The payload field of an Ethereum transaction must contain a valid
func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction { // encoded SDK transaction.
gethTx := ethtypes.NewTransaction( func (tx Transaction) GetEmbeddedTx(codec *wire.Codec) (sdk.Tx, sdk.Error) {
tx.Data.AccountNonce, *tx.Data.Recipient, tx.Data.Amount.BigInt(), var etx sdk.Tx
tx.Data.GasLimit, tx.Data.Price.BigInt(), tx.Data.Payload,
)
sig := recoverEthSig(tx.Data.Signature, chainID) err := codec.UnmarshalBinary(tx.data.Payload, &etx)
signer := ethtypes.NewEIP155Signer(chainID)
gethTx, err := gethTx.WithSignature(signer, sig)
if err != nil { if err != nil {
panic(errors.Wrap(err, "failed to convert transaction with a given signature")) return etx, sdk.ErrTxDecode("failed to decode embedded transaction")
} }
return *gethTx return etx, nil
} }
// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes, // ----------------------------------------------------------------------------
// attempts to decode them into a valid sdk.Tx. // Utilities
func TxDecoder(codec *wire.Codec) sdk.TxDecoder { // ----------------------------------------------------------------------------
// 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
} }

View File

@ -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"
) )
func TestTransactionRLPEncode(t *testing.T) {
txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
txRLP, err := rlp.EncodeToBytes(txs[0])
require.NoError(t, err)
gtxRLP, err := rlp.EncodeToBytes(gtxs[0])
require.NoError(t, err)
require.Equal(t, gtxRLP, txRLP)
}
func TestTransactionRLPDecode(t *testing.T) {
txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
txRLP, err := rlp.EncodeToBytes(txs[0])
require.NoError(t, err)
gtxRLP, err := rlp.EncodeToBytes(gtxs[0])
require.NoError(t, err)
var ( var (
testChainID = sdk.NewInt(3) decodedTx Transaction
decodedGtx ethtypes.Transaction
testPrivKey1, _ = ethcrypto.GenerateKey()
testPrivKey2, _ = ethcrypto.GenerateKey()
testAddr1 = PrivKeyToEthAddress(testPrivKey1)
testAddr2 = PrivKeyToEthAddress(testPrivKey2)
) )
func newTestCodec() *wire.Codec { err = rlp.DecodeBytes(txRLP, &decodedTx)
codec := wire.NewCodec() require.NoError(t, err)
RegisterWire(codec) err = rlp.DecodeBytes(gtxRLP, &decodedGtx)
codec.RegisterConcrete(auth.StdTx{}, "test/StdTx", nil) require.NoError(t, err)
codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
wire.RegisterCrypto(codec)
return codec require.Equal(t, decodedGtx.Hash(), decodedTx.Hash())
}
func newStdFee() auth.StdFee {
return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150)))
}
func newTestStdTx(
chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey,
accNums []int64, seqs []int64, fee auth.StdFee,
) sdk.Tx {
sigs := make([]auth.StdSignature, len(pKeys))
for i, priv := range pKeys {
signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], newStdFee(), 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 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)
} }

View File

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

View File

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

View File

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