forked from cerc-io/laconicd-deprecated
Transaction signing and encoding (#95)
* WIP setting up evm tx command and updating emint keys output * Fix linting issue * Wip restructuring to allow for ethereum signing and encoding * WIP setting up keybase and context to use Ethermint keys * Fixed encoding and decoding of emint keys * Adds command for generating explicit ethereum tx * Fixed evm route for handling tx * Fixed tx and msg encoding which allows transactions to be sent * Added relevant documentation for changes and cleaned up code * Added documentation and indicators why code was overriden
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||
emintkeys "github.com/cosmos/ethermint/keys"
|
||||
emintTypes "github.com/cosmos/ethermint/types"
|
||||
emintUtils "github.com/cosmos/ethermint/x/evm/client/utils"
|
||||
"github.com/cosmos/ethermint/x/evm/types"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// GetTxCmd defines the CLI commands regarding evm module transactions
|
||||
func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command {
|
||||
evmTxCmd := &cobra.Command{
|
||||
Use: types.ModuleName,
|
||||
Short: "EVM transaction subcommands",
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
evmTxCmd.AddCommand(client.PostCommands(
|
||||
// TODO: Add back generating cosmos tx for Ethereum tx message
|
||||
// GetCmdGenTx(cdc),
|
||||
GetCmdGenETHTx(cdc),
|
||||
)...)
|
||||
|
||||
return evmTxCmd
|
||||
}
|
||||
|
||||
// GetCmdGenTx generates an ethereum transaction wrapped in a Cosmos standard transaction
|
||||
func GetCmdGenTx(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "generate-tx [ethaddress] [amount] [gaslimit] [gasprice] [payload]",
|
||||
Short: "generate eth tx wrapped in a Cosmos Standard tx",
|
||||
Args: cobra.ExactArgs(5),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// TODO: remove inputs and infer based on StdTx
|
||||
cliCtx := emintUtils.NewETHCLIContext().WithCodec(cdc)
|
||||
|
||||
txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
|
||||
|
||||
kb, err := emintkeys.NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
coins, err := sdk.ParseCoins(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasLimit, err := strconv.ParseUint(args[2], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasPrice, err := strconv.ParseUint(args[3], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload := args[4]
|
||||
|
||||
// TODO: Remove explicit photon check and check variables
|
||||
msg := types.NewEthereumTxMsg(0, ethcmn.HexToAddress(args[0]), big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload))
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: possibly overwrite gas values in txBldr
|
||||
return emintUtils.GenerateOrBroadcastETHMsgs(cliCtx, txBldr.WithKeybase(kb), []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetCmdGenTx generates an ethereum transaction
|
||||
func GetCmdGenETHTx(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "generate-eth-tx [nonce] [ethaddress] [amount] [gaslimit] [gasprice] [payload]",
|
||||
Short: "geberate and broadcast an Ethereum tx",
|
||||
Args: cobra.ExactArgs(6),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := emintUtils.NewETHCLIContext().WithCodec(cdc)
|
||||
|
||||
txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
|
||||
|
||||
kb, err := emintkeys.NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
nonce, err := strconv.ParseUint(args[0], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
coins, err := sdk.ParseCoins(args[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasLimit, err := strconv.ParseUint(args[3], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasPrice, err := strconv.ParseUint(args[4], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload := args[5]
|
||||
|
||||
tx := types.NewEthereumTxMsg(nonce, ethcmn.HexToAddress(args[1]), big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload))
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return emintUtils.BroadcastETHTx(cliCtx, txBldr.WithSequence(nonce).WithKeybase(kb), tx)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/input"
|
||||
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
emintcrypto "github.com/cosmos/ethermint/crypto"
|
||||
emintkeys "github.com/cosmos/ethermint/keys"
|
||||
emint "github.com/cosmos/ethermint/types"
|
||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
// * Code from this file is a modified version of cosmos-sdk/auth/client/utils/tx.go
|
||||
// * to allow for using the Ethermint keybase for signing the transaction
|
||||
|
||||
// GenerateOrBroadcastMsgs creates a StdTx given a series of messages. If
|
||||
// the provided context has generate-only enabled, the tx will only be printed
|
||||
// to STDOUT in a fully offline manner. Otherwise, the tx will be signed and
|
||||
// broadcasted.
|
||||
func GenerateOrBroadcastETHMsgs(cliCtx context.CLIContext, txBldr authtypes.TxBuilder, msgs []sdk.Msg) error {
|
||||
if cliCtx.GenerateOnly {
|
||||
return utils.PrintUnsignedStdTx(txBldr, cliCtx, msgs)
|
||||
}
|
||||
|
||||
return completeAndBroadcastETHTxCLI(txBldr, cliCtx, msgs)
|
||||
}
|
||||
|
||||
// BroadcastETHTx Broadcasts an Ethereum Tx not wrapped in a Std Tx
|
||||
func BroadcastETHTx(cliCtx context.CLIContext, txBldr authtypes.TxBuilder, tx *evmtypes.EthereumTxMsg) error {
|
||||
txBldr, err := utils.PrepareTxBuilder(txBldr, cliCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromName := cliCtx.GetFromName()
|
||||
|
||||
passphrase, err := emintkeys.GetPassphrase(fromName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sign V, R, S fields for tx
|
||||
ethTx, err := signEthTx(txBldr.Keybase(), fromName, passphrase, tx, txBldr.ChainID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use default Tx Encoder since it will just be broadcasted to TM node at this point
|
||||
txEncoder := txBldr.TxEncoder()
|
||||
|
||||
txBytes, err := txEncoder(ethTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// broadcast to a Tendermint node
|
||||
res, err := cliCtx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cliCtx.PrintOutput(res)
|
||||
}
|
||||
|
||||
// completeAndBroadcastETHTxCLI implements a utility function that facilitates
|
||||
// sending a series of messages in a signed transaction given a TxBuilder and a
|
||||
// QueryContext. It ensures that the account exists, has a proper number and
|
||||
// sequence set. In addition, it builds and signs a transaction with the
|
||||
// supplied messages. Finally, it broadcasts the signed transaction to a node.
|
||||
// * Modified version from github.com/cosmos/cosmos-sdk/x/auth/client/utils/tx.go
|
||||
func completeAndBroadcastETHTxCLI(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
||||
txBldr, err := utils.PrepareTxBuilder(txBldr, cliCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromName := cliCtx.GetFromName()
|
||||
|
||||
if txBldr.SimulateAndExecute() || cliCtx.Simulate {
|
||||
txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasEst := utils.GasEstimateResponse{GasEstimate: txBldr.Gas()}
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s\n", gasEst.String())
|
||||
}
|
||||
|
||||
if cliCtx.Simulate {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !cliCtx.SkipConfirm {
|
||||
stdSignMsg, err := txBldr.BuildSignMsg(msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var json []byte
|
||||
if viper.GetBool(flags.FlagIndentResponse) {
|
||||
json, err = cliCtx.Codec.MarshalJSONIndent(stdSignMsg, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
json = cliCtx.Codec.MustMarshalJSON(stdSignMsg)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s\n\n", json)
|
||||
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf)
|
||||
if err != nil || !ok {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// * This function is overriden to change the keybase reference here
|
||||
passphrase, err := emintkeys.GetPassphrase(fromName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction
|
||||
// * needed to be modified also to change how the data is signed
|
||||
txBytes, err := buildAndSign(txBldr, fromName, passphrase, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// broadcast to a Tendermint node
|
||||
res, err := cliCtx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cliCtx.PrintOutput(res)
|
||||
}
|
||||
|
||||
// BuildAndSign builds a single message to be signed, and signs a transaction
|
||||
// with the built message given a name, passphrase, and a set of messages.
|
||||
// * overriden from github.com/cosmos/cosmos-sdk/x/auth/types/txbuilder.go
|
||||
// * This is just modified to change the functionality in makeSignature, through sign
|
||||
func buildAndSign(bldr authtypes.TxBuilder, name, passphrase string, msgs []sdk.Msg) ([]byte, error) {
|
||||
msg, err := bldr.BuildSignMsg(msgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sign(bldr, name, passphrase, msg)
|
||||
}
|
||||
|
||||
// Sign signs a transaction given a name, passphrase, and a single message to
|
||||
// signed. An error is returned if signing fails.
|
||||
func sign(bldr authtypes.TxBuilder, name, passphrase string, msg authtypes.StdSignMsg) ([]byte, error) {
|
||||
sig, err := makeSignature(bldr.Keybase(), name, passphrase, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txEncoder := bldr.TxEncoder()
|
||||
|
||||
return txEncoder(authtypes.NewStdTx(msg.Msgs, msg.Fee, []authtypes.StdSignature{sig}, msg.Memo))
|
||||
}
|
||||
|
||||
// MakeSignature builds a StdSignature given keybase, key name, passphrase, and a StdSignMsg.
|
||||
func makeSignature(keybase crkeys.Keybase, name, passphrase string,
|
||||
msg authtypes.StdSignMsg) (sig authtypes.StdSignature, err error) {
|
||||
if keybase == nil {
|
||||
// * This is overriden to allow ethermint keys, but not used because keybase is set
|
||||
keybase, err = emintkeys.NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// EthereumTxMsg always returns the data in the 0th index so it is safe to do this
|
||||
var ethTx *evmtypes.EthereumTxMsg
|
||||
ethTx, ok := msg.Msgs[0].(*evmtypes.EthereumTxMsg)
|
||||
if !ok {
|
||||
return sig, fmt.Errorf("Transaction message not an Ethereum Tx")
|
||||
}
|
||||
|
||||
// TODO: Move this logic to after tx is rlp decoded in keybase Sign function
|
||||
// parse the chainID from a string to a base-10 integer
|
||||
chainID, ok := new(big.Int).SetString(msg.ChainID, 10)
|
||||
if !ok {
|
||||
return sig, emint.ErrInvalidChainID(fmt.Sprintf("invalid chainID: %s", msg.ChainID))
|
||||
}
|
||||
|
||||
privKey, err := keybase.ExportPrivateKeyObject(name, passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
emintKey, ok := privKey.(emintcrypto.PrivKeySecp256k1)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("invalid private key type: %T", privKey))
|
||||
}
|
||||
|
||||
ethTx.Sign(chainID, emintKey.ToECDSA())
|
||||
|
||||
// * This is needed to be overriden to get bytes to sign (RLPSignBytes) with the chainID
|
||||
sigBytes, pubkey, err := keybase.Sign(name, passphrase, ethTx.RLPSignBytes(chainID).Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return authtypes.StdSignature{
|
||||
PubKey: pubkey,
|
||||
Signature: sigBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// signEthTx populates the V, R, and S fields of an EthereumTxMsg using an ethermint key
|
||||
func signEthTx(keybase crkeys.Keybase, name, passphrase string,
|
||||
ethTx *evmtypes.EthereumTxMsg, chainID string) (_ *evmtypes.EthereumTxMsg, err error) {
|
||||
if keybase == nil {
|
||||
keybase, err = emintkeys.NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// parse the chainID from a string to a base-10 integer
|
||||
intChainID, ok := new(big.Int).SetString(chainID, 10)
|
||||
if !ok {
|
||||
return ethTx, emint.ErrInvalidChainID(fmt.Sprintf("invalid chainID: %s", chainID))
|
||||
}
|
||||
|
||||
privKey, err := keybase.ExportPrivateKeyObject(name, passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Key must be a ethermint key to be able to be converted into an ECDSA private key to sign
|
||||
emintKey, ok := privKey.(emintcrypto.PrivKeySecp256k1)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("invalid private key type: %T", privKey))
|
||||
}
|
||||
|
||||
ethTx.Sign(intChainID, emintKey.ToECDSA())
|
||||
|
||||
return ethTx, err
|
||||
}
|
||||
|
||||
// * This context is needed because the previous GetFromFields function would initialize a
|
||||
// * default keybase to lookup the address or name. The new one overrides the keybase with the
|
||||
// * ethereum compatible one
|
||||
|
||||
// NewCLIContextWithFrom returns a new initialized CLIContext with parameters from the
|
||||
// command line using Viper. It takes a key name or address and populates the FromName and
|
||||
// FromAddress field accordingly.
|
||||
func NewETHCLIContext() context.CLIContext {
|
||||
var nodeURI string
|
||||
var rpc rpcclient.Client
|
||||
|
||||
from := viper.GetString(flags.FlagFrom)
|
||||
|
||||
genOnly := viper.GetBool(flags.FlagGenerateOnly)
|
||||
|
||||
// * This function is needed only to override this call to access correct keybase
|
||||
fromAddress, fromName, err := getFromFields(from, genOnly)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to get from fields: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !genOnly {
|
||||
nodeURI = viper.GetString(flags.FlagNode)
|
||||
if nodeURI != "" {
|
||||
rpc = rpcclient.NewHTTP(nodeURI, "/websocket")
|
||||
}
|
||||
}
|
||||
|
||||
return context.CLIContext{
|
||||
Client: rpc,
|
||||
Output: os.Stdout,
|
||||
NodeURI: nodeURI,
|
||||
From: viper.GetString(flags.FlagFrom),
|
||||
OutputFormat: viper.GetString(cli.OutputFlag),
|
||||
Height: viper.GetInt64(flags.FlagHeight),
|
||||
TrustNode: viper.GetBool(flags.FlagTrustNode),
|
||||
UseLedger: viper.GetBool(flags.FlagUseLedger),
|
||||
BroadcastMode: viper.GetString(flags.FlagBroadcastMode),
|
||||
// Verifier: verifier,
|
||||
Simulate: viper.GetBool(flags.FlagDryRun),
|
||||
GenerateOnly: genOnly,
|
||||
FromAddress: fromAddress,
|
||||
FromName: fromName,
|
||||
Indent: viper.GetBool(flags.FlagIndentResponse),
|
||||
SkipConfirm: viper.GetBool(flags.FlagSkipConfirmation),
|
||||
}
|
||||
}
|
||||
|
||||
// GetFromFields returns a from account address and Keybase name given either
|
||||
// an address or key name. If genOnly is true, only a valid Bech32 cosmos
|
||||
// address is returned.
|
||||
func getFromFields(from string, genOnly bool) (sdk.AccAddress, string, error) {
|
||||
if from == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
if genOnly {
|
||||
addr, err := sdk.AccAddressFromBech32(from)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "must provide a valid Bech32 address for generate-only")
|
||||
}
|
||||
|
||||
return addr, "", nil
|
||||
}
|
||||
|
||||
// * This is the line that needed to be overriden, change could be to pass in optional keybase?
|
||||
keybase, err := emintkeys.NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var info crkeys.Info
|
||||
if addr, err := sdk.AccAddressFromBech32(from); err == nil {
|
||||
info, err = keybase.GetByAddress(addr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
} else {
|
||||
info, err = keybase.Get(from)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
return info.GetAddress(), info.GetName(), nil
|
||||
}
|
||||
+1
-1
@@ -55,7 +55,7 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
|
||||
|
||||
// Get the root tx command of this module
|
||||
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
return nil // cli.GetTxCmd(StoreKey, cdc)
|
||||
return cli.GetTxCmd(types.ModuleName, cdc)
|
||||
}
|
||||
|
||||
type AppModule struct {
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ var big8 = big.NewInt(8)
|
||||
// message type and route constants
|
||||
const (
|
||||
TypeEthereumTxMsg = "ethereum_tx"
|
||||
RouteEthereumTxMsg = "evm"
|
||||
RouteEthereumTxMsg = RouterKey
|
||||
)
|
||||
|
||||
// EthereumTxMsg encapsulates an Ethereum transaction as an SDK message.
|
||||
@@ -128,11 +128,11 @@ func (msg EthereumTxMsg) Type() string { return TypeEthereumTxMsg }
|
||||
// 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("price must be positive")
|
||||
return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Data.Price))
|
||||
}
|
||||
|
||||
if msg.Data.Amount.Sign() != 1 {
|
||||
return types.ErrInvalidValue("amount must be positive")
|
||||
return types.ErrInvalidValue(fmt.Sprintf("amount must be positive: %x", msg.Data.Amount))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var cdc = codec.New()
|
||||
|
||||
func init() {
|
||||
RegisterAmino(cdc)
|
||||
}
|
||||
|
||||
// RegisterAmino registers all crypto related types in the given (amino) codec.
|
||||
func RegisterAmino(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(EncodableTxData{}, "ethermint/EncodedMessage", nil)
|
||||
}
|
||||
|
||||
// TxData implements the Ethereum transaction data structure. It is used
|
||||
// solely as intended in Ethereum abiding by the protocol.
|
||||
type EncodableTxData struct {
|
||||
AccountNonce uint64 `json:"nonce"`
|
||||
Price string `json:"gasPrice"`
|
||||
GasLimit uint64 `json:"gas"`
|
||||
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
|
||||
Amount string `json:"value"`
|
||||
Payload []byte `json:"input"`
|
||||
|
||||
// signature values
|
||||
V string `json:"v"`
|
||||
R string `json:"r"`
|
||||
S string `json:"s"`
|
||||
|
||||
// hash is only used when marshaling to JSON
|
||||
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
|
||||
}
|
||||
|
||||
func marshalAmino(td EncodableTxData) (string, error) {
|
||||
bz, err := cdc.MarshalBinaryBare(td)
|
||||
return string(bz), err
|
||||
}
|
||||
|
||||
func unmarshalAmino(td *EncodableTxData, text string) (err error) {
|
||||
return cdc.UnmarshalBinaryBare([]byte(text), td)
|
||||
}
|
||||
|
||||
func marshalBigInt(i *big.Int) string {
|
||||
bz, err := i.MarshalText()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(bz)
|
||||
}
|
||||
|
||||
func unmarshalBigInt(s string) (*big.Int, error) {
|
||||
ret := new(big.Int)
|
||||
err := ret.UnmarshalText([]byte(s))
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// MarshalAmino defines custom encoding scheme for TxData
|
||||
func (td TxData) MarshalAmino() (string, error) {
|
||||
e := EncodableTxData{
|
||||
AccountNonce: td.AccountNonce,
|
||||
Price: marshalBigInt(td.Price),
|
||||
GasLimit: td.GasLimit,
|
||||
Recipient: td.Recipient,
|
||||
Amount: marshalBigInt(td.Amount),
|
||||
Payload: td.Payload,
|
||||
|
||||
V: marshalBigInt(td.V),
|
||||
R: marshalBigInt(td.R),
|
||||
S: marshalBigInt(td.S),
|
||||
|
||||
Hash: td.Hash,
|
||||
}
|
||||
|
||||
return marshalAmino(e)
|
||||
}
|
||||
|
||||
// UnmarshalAmino defines custom decoding scheme for TxData
|
||||
func (td *TxData) UnmarshalAmino(text string) (err error) {
|
||||
e := new(EncodableTxData)
|
||||
err = unmarshalAmino(e, text)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
td.AccountNonce = e.AccountNonce
|
||||
td.GasLimit = e.GasLimit
|
||||
td.Recipient = e.Recipient
|
||||
td.Payload = e.Payload
|
||||
td.Hash = e.Hash
|
||||
|
||||
price, err := unmarshalBigInt(e.Price)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if td.Price != nil {
|
||||
td.Price.Set(price)
|
||||
} else {
|
||||
td.Price = price
|
||||
}
|
||||
|
||||
amt, err := unmarshalBigInt(e.Amount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if td.Amount != nil {
|
||||
td.Amount.Set(amt)
|
||||
} else {
|
||||
td.Amount = amt
|
||||
}
|
||||
|
||||
v, err := unmarshalBigInt(e.V)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if td.V != nil {
|
||||
td.V.Set(v)
|
||||
} else {
|
||||
td.V = v
|
||||
}
|
||||
|
||||
r, err := unmarshalBigInt(e.R)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if td.R != nil {
|
||||
td.R.Set(r)
|
||||
} else {
|
||||
td.R = r
|
||||
}
|
||||
|
||||
s, err := unmarshalBigInt(e.S)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if td.S != nil {
|
||||
td.S.Set(s)
|
||||
} else {
|
||||
td.S = s
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement JSON marshaling/ unmarshaling for this type
|
||||
|
||||
// TODO: Implement YAML marshaling/ unmarshaling for this type
|
||||
+41
-1
@@ -122,7 +122,11 @@ func TestMsgEthereumTxSig(t *testing.T) {
|
||||
|
||||
func TestMsgEthereumTxAmino(t *testing.T) {
|
||||
addr := GenerateEthAddress()
|
||||
msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
|
||||
msg := NewEthereumTxMsg(5, addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test"))
|
||||
|
||||
msg.Data.V = big.NewInt(1)
|
||||
msg.Data.R = big.NewInt(2)
|
||||
msg.Data.S = big.NewInt(3)
|
||||
|
||||
raw, err := ModuleCdc.MarshalBinaryBare(msg)
|
||||
require.NoError(t, err)
|
||||
@@ -133,3 +137,39 @@ func TestMsgEthereumTxAmino(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, msg.Data, msg2.Data)
|
||||
}
|
||||
|
||||
func TestMarshalAndUnmarshalInt(t *testing.T) {
|
||||
i := big.NewInt(3)
|
||||
m := marshalBigInt(i)
|
||||
i2, err := unmarshalBigInt(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, i, i2)
|
||||
}
|
||||
|
||||
func TestMarshalAndUnmarshalData(t *testing.T) {
|
||||
addr := GenerateEthAddress()
|
||||
hash := ethcmn.BigToHash(big.NewInt(2))
|
||||
e := EncodableTxData{
|
||||
AccountNonce: 2,
|
||||
Price: marshalBigInt(big.NewInt(3)),
|
||||
GasLimit: 1,
|
||||
Recipient: &addr,
|
||||
Amount: marshalBigInt(big.NewInt(4)),
|
||||
Payload: []byte("test"),
|
||||
|
||||
V: marshalBigInt(big.NewInt(5)),
|
||||
R: marshalBigInt(big.NewInt(6)),
|
||||
S: marshalBigInt(big.NewInt(7)),
|
||||
|
||||
Hash: &hash,
|
||||
}
|
||||
str, err := marshalAmino(e)
|
||||
require.NoError(t, err)
|
||||
|
||||
e2 := new(EncodableTxData)
|
||||
|
||||
err = unmarshalAmino(e2, str)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, e, *e2)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user