4a030335e6
* WIP implement eth_getBlockByNumber * Implemented some missing fields * Added gasLimit and updated previous fields * Implement remaining pieces for eth_getBlock including decoding txs * Add converting transaction objects into function for usability * Clean up code * format block in function for usability * Fixed formatting and cached gasLimit value
437 lines
12 KiB
Go
437 lines
12 KiB
Go
package types
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"sync/atomic"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
"github.com/cosmos/ethermint/rpc/args"
|
|
"github.com/cosmos/ethermint/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client/context"
|
|
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 = EthereumTxMsg{}
|
|
_ sdk.Tx = EthereumTxMsg{}
|
|
)
|
|
|
|
var big8 = big.NewInt(8)
|
|
|
|
// message type and route constants
|
|
const (
|
|
TypeEthereumTxMsg = "ethereum_tx"
|
|
RouteEthereumTxMsg = RouterKey
|
|
)
|
|
|
|
// EthereumTxMsg encapsulates an Ethereum transaction as an SDK message.
|
|
type (
|
|
EthereumTxMsg struct {
|
|
Data TxData
|
|
|
|
// caches
|
|
hash atomic.Value
|
|
size atomic.Value
|
|
from atomic.Value
|
|
}
|
|
|
|
// TxData implements the Ethereum transaction data structure. It is used
|
|
// solely as intended in Ethereum abiding by the protocol.
|
|
TxData struct {
|
|
AccountNonce uint64 `json:"nonce"`
|
|
Price *big.Int `json:"gasPrice"`
|
|
GasLimit uint64 `json:"gas"`
|
|
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
|
|
Amount *big.Int `json:"value"`
|
|
Payload []byte `json:"input"`
|
|
|
|
// 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 *ethcmn.Hash `json:"hash" rlp:"-"`
|
|
}
|
|
|
|
// sigCache is used to cache the derived sender and contains the signer used
|
|
// to derive it.
|
|
sigCache struct {
|
|
signer ethtypes.Signer
|
|
from ethcmn.Address
|
|
}
|
|
)
|
|
|
|
// NewEthereumTxMsg returns a reference to a new Ethereum transaction message.
|
|
func NewEthereumTxMsg(
|
|
nonce uint64, to ethcmn.Address, amount *big.Int,
|
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
) *EthereumTxMsg {
|
|
|
|
return newEthereumTxMsg(nonce, &to, amount, gasLimit, gasPrice, payload)
|
|
}
|
|
|
|
// NewEthereumTxMsgContract returns a reference to a new Ethereum transaction
|
|
// message designated for contract creation.
|
|
func NewEthereumTxMsgContract(
|
|
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
) *EthereumTxMsg {
|
|
|
|
return newEthereumTxMsg(nonce, nil, amount, gasLimit, gasPrice, payload)
|
|
}
|
|
|
|
func newEthereumTxMsg(
|
|
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
) *EthereumTxMsg {
|
|
|
|
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 &EthereumTxMsg{Data: txData}
|
|
}
|
|
|
|
// Route returns the route value of an EthereumTxMsg.
|
|
func (msg EthereumTxMsg) Route() string { return RouteEthereumTxMsg }
|
|
|
|
// Type returns the type value of an EthereumTxMsg.
|
|
func (msg EthereumTxMsg) Type() string { return TypeEthereumTxMsg }
|
|
|
|
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
|
// checks of a Transaction. If returns an sdk.Error if validation fails.
|
|
func (msg EthereumTxMsg) ValidateBasic() sdk.Error {
|
|
if msg.Data.Price.Sign() != 1 {
|
|
return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Data.Price))
|
|
}
|
|
|
|
// Amount can be 0
|
|
if msg.Data.Amount.Sign() == -1 {
|
|
return types.ErrInvalidValue(fmt.Sprintf("amount must be positive: %x", 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 EthereumTxMsg) To() *ethcmn.Address {
|
|
if msg.Data.Recipient == nil {
|
|
return nil
|
|
}
|
|
|
|
return msg.Data.Recipient
|
|
}
|
|
|
|
// GetMsgs returns a single EthereumTxMsg as an sdk.Msg.
|
|
func (msg EthereumTxMsg) 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 cannot be used as a chain ID is needed to recover the signer
|
|
// from the signature. Use 'VerifySig' instead.
|
|
func (msg EthereumTxMsg) GetSigners() []sdk.AccAddress {
|
|
panic("must use 'VerifySig' with a chain ID to get the signer")
|
|
}
|
|
|
|
// 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 EthereumTxMsg) 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 EthereumTxMsg) 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 *EthereumTxMsg) EncodeRLP(w io.Writer) error {
|
|
return rlp.Encode(w, &msg.Data)
|
|
}
|
|
|
|
// DecodeRLP implements the rlp.Decoder interface.
|
|
func (msg *EthereumTxMsg) DecodeRLP(s *rlp.Stream) error {
|
|
_, size, _ := s.Kind()
|
|
|
|
err := s.Decode(&msg.Data)
|
|
if err == nil {
|
|
msg.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Hash hashes the RLP encoding of a transaction.
|
|
func (msg *EthereumTxMsg) Hash() ethcmn.Hash {
|
|
if hash := msg.hash.Load(); hash != nil {
|
|
return hash.(ethcmn.Hash)
|
|
}
|
|
|
|
v := rlpHash(msg)
|
|
msg.hash.Store(v)
|
|
|
|
return v
|
|
}
|
|
|
|
// 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 *EthereumTxMsg) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
|
txHash := msg.RLPSignBytes(chainID)
|
|
|
|
sig, err := ethcrypto.Sign(txHash[:], priv)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if len(sig) != 65 {
|
|
panic(fmt.Sprintf("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
|
|
}
|
|
|
|
// 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 *EthereumTxMsg) 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
|
|
}
|
|
|
|
// Cost returns amount + gasprice * gaslimit.
|
|
func (msg EthereumTxMsg) Cost() *big.Int {
|
|
total := msg.Fee()
|
|
total.Add(total, msg.Data.Amount)
|
|
return total
|
|
}
|
|
|
|
// Fee returns gasprice * gaslimit.
|
|
func (msg EthereumTxMsg) 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 *EthereumTxMsg) ChainID() *big.Int {
|
|
return deriveChainID(msg.Data.V)
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Auxiliary
|
|
|
|
// TxDecoder returns an sdk.TxDecoder that can decode both auth.StdTx and
|
|
// EthereumTxMsg transactions.
|
|
func TxDecoder(cdc *codec.Codec) sdk.TxDecoder {
|
|
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
var tx sdk.Tx
|
|
|
|
if len(txBytes) == 0 {
|
|
return nil, sdk.ErrTxDecode("txBytes are empty")
|
|
}
|
|
|
|
err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error())
|
|
}
|
|
|
|
return tx, nil
|
|
}
|
|
}
|
|
|
|
// recoverEthSig recovers a signature according to the Ethereum specification and
|
|
// returns the sender or an error.
|
|
//
|
|
// Ref: Ethereum Yellow Paper (BYZANTIUM VERSION 69351d5) Appendix F
|
|
func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, error) {
|
|
if Vb.BitLen() > 8 {
|
|
return ethcmn.Address{}, errors.New("invalid signature")
|
|
}
|
|
|
|
V := byte(Vb.Uint64() - 27)
|
|
if !ethcrypto.ValidateSignatureValues(V, R, S, true) {
|
|
return ethcmn.Address{}, errors.New("invalid signature")
|
|
}
|
|
|
|
// encode the signature in uncompressed format
|
|
r, s := R.Bytes(), S.Bytes()
|
|
sig := make([]byte, 65)
|
|
|
|
copy(sig[32-len(r):32], r)
|
|
copy(sig[64-len(s):64], s)
|
|
sig[64] = V
|
|
|
|
// recover the public key from the signature
|
|
pub, err := ethcrypto.Ecrecover(sigHash[:], sig)
|
|
if err != nil {
|
|
return ethcmn.Address{}, err
|
|
}
|
|
|
|
if len(pub) == 0 || pub[0] != 4 {
|
|
return ethcmn.Address{}, errors.New("invalid public key")
|
|
}
|
|
|
|
var addr ethcmn.Address
|
|
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
|
|
|
|
return addr, nil
|
|
}
|
|
|
|
// GenerateFromArgs populates tx message with args (used in RPC API)
|
|
func GenerateFromArgs(args args.SendTxArgs, ctx context.CLIContext) (msg *EthereumTxMsg, err error) {
|
|
var nonce uint64
|
|
|
|
var gasLimit uint64
|
|
|
|
amount := (*big.Int)(args.Value)
|
|
|
|
gasPrice := (*big.Int)(args.GasPrice)
|
|
|
|
if args.GasPrice == nil {
|
|
// Set default gas price
|
|
// TODO: Change to min gas price from context once available through server/daemon
|
|
gasPrice = big.NewInt(20)
|
|
}
|
|
|
|
if args.Nonce == nil {
|
|
// Get nonce (sequence) from account
|
|
from := sdk.AccAddress(args.From.Bytes())
|
|
_, nonce, err = authtypes.NewAccountRetriever(ctx).GetAccountNumberSequence(from)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
nonce = (uint64)(*args.Nonce)
|
|
}
|
|
|
|
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
|
|
return nil, fmt.Errorf(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
|
|
}
|
|
|
|
// Sets input to either Input or Data, if both are set and not equal error above returns
|
|
var input []byte
|
|
if args.Input != nil {
|
|
input = *args.Input
|
|
} else if args.Data != nil {
|
|
input = *args.Data
|
|
}
|
|
|
|
if args.To == nil {
|
|
// Contract creation
|
|
if len(input) == 0 {
|
|
return nil, fmt.Errorf("contract creation without any data provided")
|
|
}
|
|
}
|
|
|
|
if args.Gas == nil {
|
|
// Estimate the gas usage if necessary.
|
|
// TODO: Set gas based on estimate when simulating txs are setup
|
|
gasLimit = 22000
|
|
} else {
|
|
gasLimit = (uint64)(*args.Gas)
|
|
}
|
|
|
|
return newEthereumTxMsg(nonce, args.To, amount, gasLimit, gasPrice, input), nil
|
|
}
|