8ec7edf5bd
* evm: move Keeper and Querier to /keeper package * keeper: update keeper_test.go * fix format * evm: use aliased types * bump SDK version to v0.38.1 * app: updates from new version * errors: switch sdk.Error -> error * errors: switch sdk.Error -> error. Continuation * more fixes * update app/ * update keys and client pkgs * build * fix tests * lint * minor changes * changelog * address @austinbell comments * Fix keyring usage in rpc API and CLI * fix keyring * break line * Misc cleanup (#188) * evm: move Begin and EndBlock to abci.go * evm: use expected keeper interfaces * app: use EthermintApp for integration and unit test setup * evm: remove count type; update codec * go mod verify * evm: rename msgs for consistency * evm: events * minor cleanup * lint * ante: update tests * changelog * nolint * evm: update statedb to create ethermint Account instead of BaseAccount * fix importer test * address @austinabell comments * update README * changelog * evm: update codec * rename GasLimit->Gas and Price ->GasPrice * msg cleanup and tests * cleanup TxData * fix marshaling * revert rename * move types * evm/keeper: querier tests * switch MarshalLengthPrefixed -> BinaryBare; remove panics * fix event sender * fix panic * try fix txDecoder error * evm: handler tests * evm: handler MsgEthermint test * fix tests * store logs in keeper after transition (#210) * add some comments * begin log handler test * update TransitionCSDB to return ReturnData * use rlp for result data encode/decode * update tests * implement SetBlockLogs * implement GetBlockLogs * test log set/get * update keeper get/set logs to use hash as key * fix test * move logsKey to csdb * attempt to fix test * attempt to fix test * attempt to fix test * lint * lint * lint * save logs after handling msg * update k.Logs * cleanup * remove unused * fix issues * comment out handler test * address comments * lint * fix handler test * address comments * use amino * lint * address comments * merge * fix encoding bug * minor fix * rpc: error handling * rpc: simulate only returns gasConsumed * rpc: error ineffassign * evm: handler test * go: bump version to 1.14 and SDK version to latest master * rpc: fix simulation return value * breaking changes from SDK * sdk: breaking changes; build * tests: fixes * minor fix * proto: ethermint types attempt * proto: define EthAccount proto type and extend sdk std.Codec * evm: fix panic on handler test * evm: minor state object changes * cleanup * tests: update test-importer * fix evm test * fix pubkey registration * lint * cleanup * more test checks for importer * minor change * codec fixes * rm init func * fix importer test build * fixes * test fixes * fix bloom key * rm unnecesary func * remove comment Co-authored-by: austinabell <austinabell8@gmail.com> Co-authored-by: noot <36753753+noot@users.noreply.github.com>
384 lines
10 KiB
Go
384 lines
10 KiB
Go
package types
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"sync/atomic"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/ethermint/types"
|
|
|
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
)
|
|
|
|
var (
|
|
_ sdk.Msg = MsgEthermint{}
|
|
_ sdk.Msg = MsgEthereumTx{}
|
|
_ sdk.Tx = MsgEthereumTx{}
|
|
)
|
|
|
|
var big8 = big.NewInt(8)
|
|
|
|
// message type and route constants
|
|
const (
|
|
// TypeMsgEthereumTx defines the type string of an Ethereum tranasction
|
|
TypeMsgEthereumTx = "ethereum"
|
|
// TypeMsgEthermint defines the type string of Ethermint message
|
|
TypeMsgEthermint = "ethermint"
|
|
)
|
|
|
|
// MsgEthermint implements a cosmos equivalent structure for Ethereum transactions
|
|
type MsgEthermint struct {
|
|
AccountNonce uint64 `json:"nonce"`
|
|
Price sdk.Int `json:"gasPrice"`
|
|
GasLimit uint64 `json:"gas"`
|
|
Recipient *sdk.AccAddress `json:"to" rlp:"nil"` // nil means contract creation
|
|
Amount sdk.Int `json:"value"`
|
|
Payload []byte `json:"input"`
|
|
|
|
// From address (formerly derived from signature)
|
|
From sdk.AccAddress `json:"from"`
|
|
}
|
|
|
|
// NewMsgEthermint returns a reference to a new Ethermint transaction
|
|
func NewMsgEthermint(
|
|
nonce uint64, to *sdk.AccAddress, amount sdk.Int,
|
|
gasLimit uint64, gasPrice sdk.Int, payload []byte, from sdk.AccAddress,
|
|
) MsgEthermint {
|
|
return MsgEthermint{
|
|
AccountNonce: nonce,
|
|
Price: gasPrice,
|
|
GasLimit: gasLimit,
|
|
Recipient: to,
|
|
Amount: amount,
|
|
Payload: payload,
|
|
From: from,
|
|
}
|
|
}
|
|
|
|
// Route should return the name of the module
|
|
func (msg MsgEthermint) Route() string { return RouterKey }
|
|
|
|
// Type returns the action of the message
|
|
func (msg MsgEthermint) Type() string { return TypeMsgEthermint }
|
|
|
|
// GetSignBytes encodes the message for signing
|
|
func (msg MsgEthermint) GetSignBytes() []byte {
|
|
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
|
|
}
|
|
|
|
// ValidateBasic runs stateless checks on the message
|
|
func (msg MsgEthermint) ValidateBasic() error {
|
|
if msg.Price.Sign() != 1 {
|
|
return sdkerrors.Wrapf(types.ErrInvalidValue, "price must be positive %s", msg.Price)
|
|
}
|
|
|
|
// Amount can be 0
|
|
if msg.Amount.Sign() == -1 {
|
|
return sdkerrors.Wrapf(types.ErrInvalidValue, "amount cannot be negative %s", msg.Amount)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetSigners defines whose signature is required
|
|
func (msg MsgEthermint) GetSigners() []sdk.AccAddress {
|
|
return []sdk.AccAddress{msg.From}
|
|
}
|
|
|
|
// To returns the recipient address of the transaction. It returns nil if the
|
|
// transaction is a contract creation.
|
|
func (msg MsgEthermint) To() *ethcmn.Address {
|
|
if msg.Recipient == nil {
|
|
return nil
|
|
}
|
|
|
|
addr := ethcmn.BytesToAddress(msg.Recipient.Bytes())
|
|
return &addr
|
|
}
|
|
|
|
// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.
|
|
type MsgEthereumTx struct {
|
|
Data TxData
|
|
|
|
// caches
|
|
hash atomic.Value
|
|
size atomic.Value
|
|
from atomic.Value
|
|
}
|
|
|
|
// sigCache is used to cache the derived sender and contains the signer used
|
|
// to derive it.
|
|
type sigCache struct {
|
|
signer ethtypes.Signer
|
|
from ethcmn.Address
|
|
}
|
|
|
|
// NewMsgEthereumTx returns a reference to a new Ethereum transaction message.
|
|
func NewMsgEthereumTx(
|
|
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
) MsgEthereumTx {
|
|
return newMsgEthereumTx(nonce, to, amount, gasLimit, gasPrice, payload)
|
|
}
|
|
|
|
// NewMsgEthereumTxContract returns a reference to a new Ethereum transaction
|
|
// message designated for contract creation.
|
|
func NewMsgEthereumTxContract(
|
|
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
) MsgEthereumTx {
|
|
return newMsgEthereumTx(nonce, nil, amount, gasLimit, gasPrice, payload)
|
|
}
|
|
|
|
func newMsgEthereumTx(
|
|
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
) MsgEthereumTx {
|
|
if len(payload) > 0 {
|
|
payload = ethcmn.CopyBytes(payload)
|
|
}
|
|
|
|
txData := TxData{
|
|
AccountNonce: nonce,
|
|
Recipient: to,
|
|
Payload: payload,
|
|
GasLimit: gasLimit,
|
|
Amount: new(big.Int),
|
|
Price: new(big.Int),
|
|
V: new(big.Int),
|
|
R: new(big.Int),
|
|
S: new(big.Int),
|
|
}
|
|
|
|
if amount != nil {
|
|
txData.Amount.Set(amount)
|
|
}
|
|
if gasPrice != nil {
|
|
txData.Price.Set(gasPrice)
|
|
}
|
|
|
|
return MsgEthereumTx{Data: txData}
|
|
}
|
|
|
|
// Route returns the route value of an MsgEthereumTx.
|
|
func (msg MsgEthereumTx) Route() string { return RouterKey }
|
|
|
|
// Type returns the type value of an MsgEthereumTx.
|
|
func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx }
|
|
|
|
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
|
// checks of a Transaction. If returns an error if validation fails.
|
|
func (msg MsgEthereumTx) ValidateBasic() error {
|
|
if msg.Data.Price.Sign() != 1 {
|
|
return sdkerrors.Wrapf(types.ErrInvalidValue, "price must be positive %s", msg.Data.Price)
|
|
}
|
|
|
|
// Amount can be 0
|
|
if msg.Data.Amount.Sign() == -1 {
|
|
return sdkerrors.Wrapf(types.ErrInvalidValue, "amount cannot be negative %s", msg.Data.Amount)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// To returns the recipient address of the transaction. It returns nil if the
|
|
// transaction is a contract creation.
|
|
func (msg MsgEthereumTx) To() *ethcmn.Address {
|
|
return msg.Data.Recipient
|
|
}
|
|
|
|
// GetMsgs returns a single MsgEthereumTx as an sdk.Msg.
|
|
func (msg MsgEthereumTx) GetMsgs() []sdk.Msg {
|
|
return []sdk.Msg{msg}
|
|
}
|
|
|
|
// GetSigners returns the expected signers for an Ethereum transaction message.
|
|
// For such a message, there should exist only a single 'signer'.
|
|
//
|
|
// NOTE: This method panics if 'VerifySig' hasn't been called first.
|
|
func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress {
|
|
sender := msg.From()
|
|
if sender.Empty() {
|
|
panic("must use 'VerifySig' with a chain ID to get the signer")
|
|
}
|
|
return []sdk.AccAddress{sender}
|
|
}
|
|
|
|
// GetSignBytes returns the Amino bytes of an Ethereum transaction message used
|
|
// for signing.
|
|
//
|
|
// NOTE: This method cannot be used as a chain ID is needed to create valid bytes
|
|
// to sign over. Use 'RLPSignBytes' instead.
|
|
func (msg MsgEthereumTx) GetSignBytes() []byte {
|
|
panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign")
|
|
}
|
|
|
|
// RLPSignBytes returns the RLP hash of an Ethereum transaction message with a
|
|
// given chainID used for signing.
|
|
func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
|
|
return rlpHash([]interface{}{
|
|
msg.Data.AccountNonce,
|
|
msg.Data.Price,
|
|
msg.Data.GasLimit,
|
|
msg.Data.Recipient,
|
|
msg.Data.Amount,
|
|
msg.Data.Payload,
|
|
chainID, uint(0), uint(0),
|
|
})
|
|
}
|
|
|
|
// EncodeRLP implements the rlp.Encoder interface.
|
|
func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error {
|
|
return rlp.Encode(w, &msg.Data)
|
|
}
|
|
|
|
// DecodeRLP implements the rlp.Decoder interface.
|
|
func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
|
|
_, size, err := s.Kind()
|
|
if err != nil {
|
|
// return error if stream is too large
|
|
return err
|
|
}
|
|
|
|
if err := s.Decode(&msg.Data); err != nil {
|
|
return err
|
|
}
|
|
|
|
msg.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
|
|
return nil
|
|
}
|
|
|
|
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
|
|
// 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
|
|
// fields of the Transaction's Signature.
|
|
func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) error {
|
|
txHash := msg.RLPSignBytes(chainID)
|
|
|
|
sig, err := ethcrypto.Sign(txHash[:], priv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(sig) != 65 {
|
|
return fmt.Errorf("wrong size for signature: got %d, want 65", len(sig))
|
|
}
|
|
|
|
r := new(big.Int).SetBytes(sig[:32])
|
|
s := new(big.Int).SetBytes(sig[32:64])
|
|
|
|
var v *big.Int
|
|
|
|
if chainID.Sign() == 0 {
|
|
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
|
|
} else {
|
|
v = big.NewInt(int64(sig[64] + 35))
|
|
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
|
|
|
v.Add(v, chainIDMul)
|
|
}
|
|
|
|
msg.Data.V = v
|
|
msg.Data.R = r
|
|
msg.Data.S = s
|
|
return nil
|
|
}
|
|
|
|
// 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 (msg *MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
|
|
signer := ethtypes.NewEIP155Signer(chainID)
|
|
|
|
if sc := msg.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("chainID cannot be zero")
|
|
}
|
|
|
|
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
|
V := new(big.Int).Sub(msg.Data.V, chainIDMul)
|
|
V.Sub(V, big8)
|
|
|
|
sigHash := msg.RLPSignBytes(chainID)
|
|
sender, err := recoverEthSig(msg.Data.R, msg.Data.S, V, sigHash)
|
|
if err != nil {
|
|
return ethcmn.Address{}, err
|
|
}
|
|
|
|
msg.from.Store(sigCache{signer: signer, from: sender})
|
|
return sender, nil
|
|
}
|
|
|
|
// GetGas implements the GasTx interface. It returns the GasLimit of the transaction.
|
|
func (msg MsgEthereumTx) GetGas() uint64 {
|
|
return msg.Data.GasLimit
|
|
}
|
|
|
|
// Fee returns gasprice * gaslimit.
|
|
func (msg MsgEthereumTx) Fee() *big.Int {
|
|
return new(big.Int).Mul(msg.Data.Price, new(big.Int).SetUint64(msg.Data.GasLimit))
|
|
}
|
|
|
|
// ChainID returns which chain id this transaction was signed for (if at all)
|
|
func (msg *MsgEthereumTx) ChainID() *big.Int {
|
|
return deriveChainID(msg.Data.V)
|
|
}
|
|
|
|
// Cost returns amount + gasprice * gaslimit.
|
|
func (msg MsgEthereumTx) Cost() *big.Int {
|
|
total := msg.Fee()
|
|
total.Add(total, msg.Data.Amount)
|
|
return total
|
|
}
|
|
|
|
// RawSignatureValues returns the V, R, S signature values of the transaction.
|
|
// The return values should not be modified by the caller.
|
|
func (msg MsgEthereumTx) RawSignatureValues() (v, r, s *big.Int) {
|
|
return msg.Data.V, msg.Data.R, msg.Data.S
|
|
}
|
|
|
|
// From loads the ethereum sender address from the sigcache and returns an
|
|
// sdk.AccAddress from its bytes
|
|
func (msg *MsgEthereumTx) From() sdk.AccAddress {
|
|
sc := msg.from.Load()
|
|
if sc == nil {
|
|
return nil
|
|
}
|
|
|
|
sigCache := sc.(sigCache)
|
|
|
|
if len(sigCache.from.Bytes()) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return sdk.AccAddress(sigCache.from.Bytes())
|
|
}
|
|
|
|
// deriveChainID derives the chain id from the given v parameter
|
|
func deriveChainID(v *big.Int) *big.Int {
|
|
if v.BitLen() <= 64 {
|
|
v := v.Uint64()
|
|
if v == 27 || v == 28 {
|
|
return new(big.Int)
|
|
}
|
|
return new(big.Int).SetUint64((v - 35) / 2)
|
|
}
|
|
v = new(big.Int).Sub(v, big.NewInt(35))
|
|
return v.Div(v, big.NewInt(2))
|
|
}
|