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:
parent
4c29c48905
commit
72fc3ca3af
@ -231,7 +231,7 @@ func validateEthTxCheckTx(
|
|||||||
// validate sender/signature
|
// validate sender/signature
|
||||||
signer, err := ethTxMsg.VerifySig(chainID)
|
signer, err := ethTxMsg.VerifySig(chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdk.ErrUnauthorized("signature verification failed").Result()
|
return sdk.ErrUnauthorized(fmt.Sprintf("signature verification failed: %s", err)).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate account (nonce and balance checks)
|
// validate account (nonce and balance checks)
|
||||||
|
@ -13,6 +13,9 @@ import (
|
|||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
emintkeys "github.com/cosmos/ethermint/keys"
|
emintkeys "github.com/cosmos/ethermint/keys"
|
||||||
|
|
||||||
|
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||||
|
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
|
||||||
|
|
||||||
emintapp "github.com/cosmos/ethermint/app"
|
emintapp "github.com/cosmos/ethermint/app"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -48,7 +51,7 @@ func main() {
|
|||||||
sdkrpc.StatusCommand(),
|
sdkrpc.StatusCommand(),
|
||||||
client.ConfigCmd(emintapp.DefaultCLIHome),
|
client.ConfigCmd(emintapp.DefaultCLIHome),
|
||||||
queryCmd(cdc),
|
queryCmd(cdc),
|
||||||
// TODO: Set up tx command
|
txCmd(cdc),
|
||||||
// TODO: Set up rest routes (if included, different from web3 api)
|
// TODO: Set up rest routes (if included, different from web3 api)
|
||||||
rpc.Web3RpcCmd(cdc),
|
rpc.Web3RpcCmd(cdc),
|
||||||
client.LineBreak,
|
client.LineBreak,
|
||||||
@ -83,6 +86,28 @@ func queryCmd(cdc *amino.Codec) *cobra.Command {
|
|||||||
return queryCmd
|
return queryCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func txCmd(cdc *amino.Codec) *cobra.Command {
|
||||||
|
txCmd := &cobra.Command{
|
||||||
|
Use: "tx",
|
||||||
|
Short: "Transactions subcommands",
|
||||||
|
}
|
||||||
|
|
||||||
|
txCmd.AddCommand(
|
||||||
|
bankcmd.SendTxCmd(cdc),
|
||||||
|
client.LineBreak,
|
||||||
|
authcmd.GetSignCommand(cdc),
|
||||||
|
client.LineBreak,
|
||||||
|
authcmd.GetBroadcastCommand(cdc),
|
||||||
|
authcmd.GetEncodeCommand(cdc),
|
||||||
|
client.LineBreak,
|
||||||
|
)
|
||||||
|
|
||||||
|
// add modules' tx commands
|
||||||
|
emintapp.ModuleBasics.AddTxCommands(txCmd, cdc)
|
||||||
|
|
||||||
|
return txCmd
|
||||||
|
}
|
||||||
|
|
||||||
func initConfig(cmd *cobra.Command) error {
|
func initConfig(cmd *cobra.Command) error {
|
||||||
home, err := cmd.PersistentFlags().GetString(cli.HomeFlag)
|
home, err := cmd.PersistentFlags().GetString(cli.HomeFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -6,6 +6,12 @@ import (
|
|||||||
|
|
||||||
var cryptoCodec = codec.New()
|
var cryptoCodec = codec.New()
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Amino encoding names
|
||||||
|
PrivKeyAminoName = "crypto/PrivKeySecp256k1"
|
||||||
|
PubKeyAminoName = "crypto/PubKeySecp256k1"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterCodec(cryptoCodec)
|
RegisterCodec(cryptoCodec)
|
||||||
}
|
}
|
||||||
@ -13,6 +19,7 @@ func init() {
|
|||||||
// RegisterCodec registers all the necessary types with amino for the given
|
// RegisterCodec registers all the necessary types with amino for the given
|
||||||
// codec.
|
// codec.
|
||||||
func RegisterCodec(cdc *codec.Codec) {
|
func RegisterCodec(cdc *codec.Codec) {
|
||||||
cdc.RegisterConcrete(PubKeySecp256k1{}, "crypto/PubKeySecp256k1", nil)
|
cdc.RegisterConcrete(PubKeySecp256k1{}, PubKeyAminoName, nil)
|
||||||
cdc.RegisterConcrete(PrivKeySecp256k1{}, "crypto/PrivKeySecp256k1", nil)
|
|
||||||
|
cdc.RegisterConcrete(PrivKeySecp256k1{}, PrivKeyAminoName, nil)
|
||||||
}
|
}
|
||||||
|
35
crypto/encoding/amino.go
Normal file
35
crypto/encoding/amino.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
emintcrypto "github.com/cosmos/ethermint/crypto"
|
||||||
|
amino "github.com/tendermint/go-amino"
|
||||||
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cdc = amino.NewCodec()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterAmino(cdc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterAmino registers all crypto related types in the given (amino) codec.
|
||||||
|
func RegisterAmino(cdc *amino.Codec) {
|
||||||
|
// These are all written here instead of
|
||||||
|
cdc.RegisterInterface((*tmcrypto.PubKey)(nil), nil)
|
||||||
|
cdc.RegisterConcrete(emintcrypto.PubKeySecp256k1{}, emintcrypto.PubKeyAminoName, nil)
|
||||||
|
|
||||||
|
cdc.RegisterInterface((*tmcrypto.PrivKey)(nil), nil)
|
||||||
|
cdc.RegisterConcrete(emintcrypto.PrivKeySecp256k1{}, emintcrypto.PrivKeyAminoName, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivKeyFromBytes unmarshalls emint private key from encoded bytes
|
||||||
|
func PrivKeyFromBytes(privKeyBytes []byte) (privKey tmcrypto.PrivKey, err error) {
|
||||||
|
err = cdc.UnmarshalBinaryBare(privKeyBytes, &privKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubKeyFromBytes unmarshalls emint public key from encoded bytes
|
||||||
|
func PubKeyFromBytes(pubKeyBytes []byte) (pubKey tmcrypto.PubKey, err error) {
|
||||||
|
err = cdc.UnmarshalBinaryBare(pubKeyBytes, &pubKey)
|
||||||
|
return
|
||||||
|
}
|
28
crypto/encoding/amino_test.go
Normal file
28
crypto/encoding/amino_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
emintcrypto "github.com/cosmos/ethermint/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeyEncodingDecoding(t *testing.T) {
|
||||||
|
// Priv Key encoding and decoding
|
||||||
|
privKey, err := emintcrypto.GenerateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
privBytes := privKey.Bytes()
|
||||||
|
|
||||||
|
decodedPriv, err := PrivKeyFromBytes(privBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, privKey, decodedPriv)
|
||||||
|
|
||||||
|
// Pub key encoding and decoding
|
||||||
|
pubKey := privKey.PubKey()
|
||||||
|
pubBytes := pubKey.Bytes()
|
||||||
|
|
||||||
|
decodedPub, err := PubKeyFromBytes(pubBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, pubKey, decodedPub)
|
||||||
|
}
|
@ -13,7 +13,7 @@ import (
|
|||||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
"github.com/cosmos/ethermint/crypto/keys/mintkey"
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
bip39 "github.com/cosmos/go-bip39"
|
bip39 "github.com/cosmos/go-bip39"
|
||||||
|
157
crypto/keys/mintkey/mintkey.go
Normal file
157
crypto/keys/mintkey/mintkey.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
package mintkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tendermint/crypto/bcrypt"
|
||||||
|
|
||||||
|
emintEncoding "github.com/cosmos/ethermint/crypto/encoding"
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
"github.com/tendermint/tendermint/crypto/armor"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/crypto/xsalsa20symmetric"
|
||||||
|
|
||||||
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
blockTypePrivKey = "ETHERMINT PRIVATE KEY"
|
||||||
|
blockTypeKeyInfo = "ETHERMINT KEY INFO"
|
||||||
|
blockTypePubKey = "ETHERMINT PUBLIC KEY"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make bcrypt security parameter var, so it can be changed within the lcd test
|
||||||
|
// Making the bcrypt security parameter a var shouldn't be a security issue:
|
||||||
|
// One can't verify an invalid key by maliciously changing the bcrypt
|
||||||
|
// parameter during a runtime vulnerability. The main security
|
||||||
|
// threat this then exposes would be something that changes this during
|
||||||
|
// runtime before the user creates their key. This vulnerability must
|
||||||
|
// succeed to update this to that same value before every subsequent call
|
||||||
|
// to the keys command in future startups / or the attacker must get access
|
||||||
|
// to the filesystem. However, with a similar threat model (changing
|
||||||
|
// variables in runtime), one can cause the user to sign a different tx
|
||||||
|
// than what they see, which is a significantly cheaper attack then breaking
|
||||||
|
// a bcrypt hash. (Recall that the nonce still exists to break rainbow tables)
|
||||||
|
// For further notes on security parameter choice, see README.md
|
||||||
|
var BcryptSecurityParameter = 12
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
// add armor
|
||||||
|
|
||||||
|
// Armor the InfoBytes
|
||||||
|
func ArmorInfoBytes(bz []byte) string {
|
||||||
|
return armorBytes(bz, blockTypeKeyInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Armor the PubKeyBytes
|
||||||
|
func ArmorPubKeyBytes(bz []byte) string {
|
||||||
|
return armorBytes(bz, blockTypePubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func armorBytes(bz []byte, blockType string) string {
|
||||||
|
header := map[string]string{
|
||||||
|
"type": "Info",
|
||||||
|
"version": "0.0.0",
|
||||||
|
}
|
||||||
|
return armor.EncodeArmor(blockType, header, bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
// remove armor
|
||||||
|
|
||||||
|
// Unarmor the InfoBytes
|
||||||
|
func UnarmorInfoBytes(armorStr string) (bz []byte, err error) {
|
||||||
|
return unarmorBytes(armorStr, blockTypeKeyInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unarmor the PubKeyBytes
|
||||||
|
func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
|
||||||
|
return unarmorBytes(armorStr, blockTypePubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
|
||||||
|
bType, header, bz, err := armor.DecodeArmor(armorStr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if bType != blockType {
|
||||||
|
err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if header["version"] != "0.0.0" {
|
||||||
|
err = fmt.Errorf("Unrecognized version: %v", header["version"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
// encrypt/decrypt with armor
|
||||||
|
|
||||||
|
// Encrypt and armor the private key.
|
||||||
|
func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
||||||
|
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
|
||||||
|
header := map[string]string{
|
||||||
|
"kdf": "bcrypt",
|
||||||
|
"salt": fmt.Sprintf("%X", saltBytes),
|
||||||
|
}
|
||||||
|
armorStr := armor.EncodeArmor(blockTypePrivKey, header, encBytes)
|
||||||
|
return armorStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt the given privKey with the passphrase using a randomly
|
||||||
|
// generated salt and the xsalsa20 cipher. returns the salt and the
|
||||||
|
// encrypted priv key.
|
||||||
|
func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
|
||||||
|
saltBytes = crypto.CRandBytes(16)
|
||||||
|
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||||
|
if err != nil {
|
||||||
|
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||||||
|
}
|
||||||
|
key = crypto.Sha256(key) // get 32 bytes
|
||||||
|
privKeyBytes := privKey.Bytes()
|
||||||
|
return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unarmor and decrypt the private key.
|
||||||
|
func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
|
||||||
|
var privKey crypto.PrivKey
|
||||||
|
blockType, header, encBytes, err := armor.DecodeArmor(armorStr)
|
||||||
|
if err != nil {
|
||||||
|
return privKey, err
|
||||||
|
}
|
||||||
|
if blockType != blockTypePrivKey {
|
||||||
|
return privKey, fmt.Errorf("Unrecognized armor type: %v", blockType)
|
||||||
|
}
|
||||||
|
if header["kdf"] != "bcrypt" {
|
||||||
|
return privKey, fmt.Errorf("Unrecognized KDF type: %v", header["KDF"])
|
||||||
|
}
|
||||||
|
if header["salt"] == "" {
|
||||||
|
return privKey, fmt.Errorf("Missing salt bytes")
|
||||||
|
}
|
||||||
|
saltBytes, err := hex.DecodeString(header["salt"])
|
||||||
|
if err != nil {
|
||||||
|
return privKey, fmt.Errorf("Error decoding salt: %v", err.Error())
|
||||||
|
}
|
||||||
|
privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase)
|
||||||
|
return privKey, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
|
||||||
|
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||||
|
if err != nil {
|
||||||
|
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||||||
|
}
|
||||||
|
key = crypto.Sha256(key) // Get 32 bytes
|
||||||
|
privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key)
|
||||||
|
if err != nil && err.Error() == "Ciphertext decryption failed" {
|
||||||
|
return privKey, keyerror.NewErrWrongPassword()
|
||||||
|
} else if err != nil {
|
||||||
|
return privKey, err
|
||||||
|
}
|
||||||
|
privKey, err = emintEncoding.PrivKeyFromBytes(privKeyBytes)
|
||||||
|
return privKey, err
|
||||||
|
}
|
@ -3,6 +3,8 @@ package keys
|
|||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,16 +14,30 @@ type KeyOutput struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
ETHAddress string `json:"ethaddress"`
|
||||||
PubKey string `json:"pubkey"`
|
PubKey string `json:"pubkey"`
|
||||||
|
ETHPubKey string `json:"ethpubkey"`
|
||||||
Mnemonic string `json:"mnemonic,omitempty"`
|
Mnemonic string `json:"mnemonic,omitempty"`
|
||||||
Threshold uint `json:"threshold,omitempty"`
|
Threshold uint `json:"threshold,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewKeyOutput creates a default KeyOutput instance without Mnemonic, Threshold and PubKeys
|
||||||
|
func NewKeyOutput(name, keyType, address, ethaddress, pubkey, ethpubkey string) KeyOutput {
|
||||||
|
return KeyOutput{
|
||||||
|
Name: name,
|
||||||
|
Type: keyType,
|
||||||
|
Address: address,
|
||||||
|
ETHAddress: ethaddress,
|
||||||
|
PubKey: pubkey,
|
||||||
|
ETHPubKey: ethpubkey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Bech32KeysOutput returns a slice of KeyOutput objects, each with the "acc"
|
// Bech32KeysOutput returns a slice of KeyOutput objects, each with the "acc"
|
||||||
// Bech32 prefixes, given a slice of Info objects. It returns an error if any
|
// Bech32 prefixes, given a slice of Info objects. It returns an error if any
|
||||||
// call to Bech32KeyOutput fails.
|
// call to Bech32KeyOutput fails.
|
||||||
func Bech32KeysOutput(infos []cosmosKeys.Info) ([]cosmosKeys.KeyOutput, error) {
|
func Bech32KeysOutput(infos []cosmosKeys.Info) ([]KeyOutput, error) {
|
||||||
kos := make([]cosmosKeys.KeyOutput, len(infos))
|
kos := make([]KeyOutput, len(infos))
|
||||||
for i, info := range infos {
|
for i, info := range infos {
|
||||||
ko, err := Bech32KeyOutput(info)
|
ko, err := Bech32KeyOutput(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -34,52 +50,62 @@ func Bech32KeysOutput(infos []cosmosKeys.Info) ([]cosmosKeys.KeyOutput, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes.
|
// Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes.
|
||||||
func Bech32ConsKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) {
|
func Bech32ConsKeyOutput(keyInfo cosmosKeys.Info) (KeyOutput, error) {
|
||||||
consAddr := keyInfo.GetPubKey().Address()
|
address := keyInfo.GetPubKey().Address()
|
||||||
bytes := keyInfo.GetPubKey().Bytes()
|
|
||||||
|
|
||||||
// bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey())
|
bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey())
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return KeyOutput{}, err
|
return KeyOutput{}, err
|
||||||
// }
|
}
|
||||||
|
|
||||||
return cosmosKeys.KeyOutput{
|
return NewKeyOutput(
|
||||||
Name: keyInfo.GetName(),
|
keyInfo.GetName(),
|
||||||
Type: keyInfo.GetType().String(),
|
keyInfo.GetType().String(),
|
||||||
Address: consAddr.String(),
|
sdk.ConsAddress(address.Bytes()).String(),
|
||||||
PubKey: hex.EncodeToString(bytes),
|
getEthAddress(keyInfo),
|
||||||
}, nil
|
bechPubKey,
|
||||||
|
hex.EncodeToString(keyInfo.GetPubKey().Bytes()),
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes.
|
// Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes.
|
||||||
func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) {
|
func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (KeyOutput, error) {
|
||||||
valAddr := keyInfo.GetPubKey().Address()
|
address := keyInfo.GetPubKey().Address()
|
||||||
bytes := keyInfo.GetPubKey().Bytes()
|
|
||||||
|
|
||||||
// bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey())
|
bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey())
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return KeyOutput{}, err
|
return KeyOutput{}, err
|
||||||
// }
|
}
|
||||||
|
|
||||||
return cosmosKeys.KeyOutput{
|
return NewKeyOutput(
|
||||||
Name: keyInfo.GetName(),
|
keyInfo.GetName(),
|
||||||
Type: keyInfo.GetType().String(),
|
keyInfo.GetType().String(),
|
||||||
Address: valAddr.String(),
|
sdk.ValAddress(address.Bytes()).String(),
|
||||||
PubKey: hex.EncodeToString(bytes),
|
getEthAddress(keyInfo),
|
||||||
}, nil
|
bechPubKey,
|
||||||
|
hex.EncodeToString(keyInfo.GetPubKey().Bytes()),
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes.
|
// Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes.
|
||||||
func Bech32KeyOutput(info cosmosKeys.Info) (cosmosKeys.KeyOutput, error) {
|
func Bech32KeyOutput(keyInfo cosmosKeys.Info) (KeyOutput, error) {
|
||||||
accAddr := info.GetPubKey().Address()
|
address := keyInfo.GetPubKey().Address()
|
||||||
bytes := info.GetPubKey().Bytes()
|
|
||||||
|
|
||||||
ko := cosmosKeys.KeyOutput{
|
bechPubKey, err := sdk.Bech32ifyAccPub(keyInfo.GetPubKey())
|
||||||
Name: info.GetName(),
|
if err != nil {
|
||||||
Type: info.GetType().String(),
|
return KeyOutput{}, err
|
||||||
Address: accAddr.String(),
|
|
||||||
PubKey: hex.EncodeToString(bytes),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ko, nil
|
return NewKeyOutput(
|
||||||
|
keyInfo.GetName(),
|
||||||
|
keyInfo.GetType().String(),
|
||||||
|
sdk.AccAddress(address.Bytes()).String(),
|
||||||
|
getEthAddress(keyInfo),
|
||||||
|
bechPubKey,
|
||||||
|
hex.EncodeToString(keyInfo.GetPubKey().Bytes()),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEthAddress(info cosmosKeys.Info) string {
|
||||||
|
return info.GetPubKey().Address().String()
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey {
|
|||||||
|
|
||||||
// Bytes returns the raw ECDSA private key bytes.
|
// Bytes returns the raw ECDSA private key bytes.
|
||||||
func (privkey PrivKeySecp256k1) Bytes() []byte {
|
func (privkey PrivKeySecp256k1) Bytes() []byte {
|
||||||
return privkey
|
return cryptoCodec.MustMarshalBinaryBare(privkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign creates a recoverable ECDSA signature on the secp256k1 curve over the
|
// Sign creates a recoverable ECDSA signature on the secp256k1 curve over the
|
||||||
@ -59,7 +59,7 @@ func (privkey PrivKeySecp256k1) Equals(other tmcrypto.PrivKey) bool {
|
|||||||
|
|
||||||
// ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type.
|
// ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type.
|
||||||
func (privkey PrivKeySecp256k1) ToECDSA() *ecdsa.PrivateKey {
|
func (privkey PrivKeySecp256k1) ToECDSA() *ecdsa.PrivateKey {
|
||||||
key, _ := ethcrypto.ToECDSA(privkey.Bytes())
|
key, _ := ethcrypto.ToECDSA(privkey)
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,11 @@ func (key PubKeySecp256k1) Address() tmcrypto.Address {
|
|||||||
|
|
||||||
// Bytes returns the raw bytes of the ECDSA public key.
|
// Bytes returns the raw bytes of the ECDSA public key.
|
||||||
func (key PubKeySecp256k1) Bytes() []byte {
|
func (key PubKeySecp256k1) Bytes() []byte {
|
||||||
return key
|
bz, err := cryptoCodec.MarshalBinaryBare(key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bz
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyBytes verifies that the ECDSA public key created a given signature over
|
// VerifyBytes verifies that the ECDSA public key created a given signature over
|
||||||
@ -93,7 +97,7 @@ func (key PubKeySecp256k1) VerifyBytes(msg []byte, sig []byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the signature needs to be in [R || S] format when provided to VerifySignature
|
// the signature needs to be in [R || S] format when provided to VerifySignature
|
||||||
return ethsecp256k1.VerifySignature(key.Bytes(), ethcrypto.Keccak256Hash(msg).Bytes(), sig)
|
return ethsecp256k1.VerifySignature(key, ethcrypto.Keccak256Hash(msg).Bytes(), sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals returns true if two ECDSA public keys are equal and false otherwise.
|
// Equals returns true if two ECDSA public keys are equal and false otherwise.
|
||||||
|
@ -30,7 +30,7 @@ func TestPrivKeySecp256k1PrivKey(t *testing.T) {
|
|||||||
// validate we can sign some bytes
|
// validate we can sign some bytes
|
||||||
msg := []byte("hello world")
|
msg := []byte("hello world")
|
||||||
sigHash := ethcrypto.Keccak256Hash(msg)
|
sigHash := ethcrypto.Keccak256Hash(msg)
|
||||||
expectedSig, _ := ethsecp256k1.Sign(sigHash.Bytes(), privKey.Bytes())
|
expectedSig, _ := ethsecp256k1.Sign(sigHash.Bytes(), privKey)
|
||||||
|
|
||||||
sig, err := privKey.Sign(msg)
|
sig, err := privKey.Sign(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
1
go.mod
1
go.mod
@ -43,6 +43,7 @@ require (
|
|||||||
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect
|
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect
|
||||||
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect
|
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
|
github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5
|
||||||
github.com/tendermint/go-amino v0.15.0
|
github.com/tendermint/go-amino v0.15.0
|
||||||
github.com/tendermint/tendermint v0.32.2
|
github.com/tendermint/tendermint v0.32.2
|
||||||
github.com/tendermint/tm-db v0.1.1
|
github.com/tendermint/tm-db v0.1.1
|
||||||
|
@ -90,7 +90,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
|||||||
interactive := viper.GetBool(flagInteractive)
|
interactive := viper.GetBool(flagInteractive)
|
||||||
showMnemonic := !viper.GetBool(flagNoBackup)
|
showMnemonic := !viper.GetBool(flagNoBackup)
|
||||||
|
|
||||||
kb, err = clientkeys.NewKeyBaseFromHomeFlag()
|
kb, err = NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
|
|
||||||
"github.com/cosmos/cosmos-sdk/tests"
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ func TestRunShowCmd(t *testing.T) {
|
|||||||
|
|
||||||
fakeKeyName1 := "runShowCmd_Key1"
|
fakeKeyName1 := "runShowCmd_Key1"
|
||||||
fakeKeyName2 := "runShowCmd_Key2"
|
fakeKeyName2 := "runShowCmd_Key2"
|
||||||
kb, err := clientkeys.NewKeyBaseFromHomeFlag()
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
|
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -2,28 +2,33 @@ package keys
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/tendermint/tendermint/libs/cli"
|
"github.com/tendermint/tendermint/libs/cli"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
|
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
emintKeys "github.com/cosmos/ethermint/crypto/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// available output formats.
|
// available output formats.
|
||||||
const (
|
const (
|
||||||
OutputFormatText = "text"
|
OutputFormatText = "text"
|
||||||
OutputFormatJSON = "json"
|
OutputFormatJSON = "json"
|
||||||
|
|
||||||
|
defaultKeyDBName = "emintkeys"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bechKeyOutFn func(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error)
|
type bechKeyOutFn func(keyInfo cosmosKeys.Info) (emintKeys.KeyOutput, error)
|
||||||
|
|
||||||
// GetKeyInfo returns key info for a given name. An error is returned if the
|
// GetKeyInfo returns key info for a given name. An error is returned if the
|
||||||
// keybase cannot be retrieved or getting the info fails.
|
// keybase cannot be retrieved or getting the info fails.
|
||||||
func GetKeyInfo(name string) (cosmosKeys.Info, error) {
|
func GetKeyInfo(name string) (cosmosKeys.Info, error) {
|
||||||
keybase, err := clientkeys.NewKeyBaseFromHomeFlag()
|
keybase, err := NewKeyBaseFromHomeFlag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -31,6 +36,47 @@ func GetKeyInfo(name string) (cosmosKeys.Info, error) {
|
|||||||
return keybase.Get(name)
|
return keybase.Get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration.
|
||||||
|
func NewKeyBaseFromHomeFlag() (cosmosKeys.Keybase, error) {
|
||||||
|
rootDir := viper.GetString(flags.FlagHome)
|
||||||
|
return NewKeyBaseFromDir(rootDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyBaseFromDir initializes a keybase at a particular dir.
|
||||||
|
func NewKeyBaseFromDir(rootDir string) (cosmosKeys.Keybase, error) {
|
||||||
|
return getLazyKeyBaseFromDir(rootDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInMemoryKeyBase returns a storage-less keybase.
|
||||||
|
func NewInMemoryKeyBase() cosmosKeys.Keybase { return emintKeys.NewInMemory() }
|
||||||
|
|
||||||
|
func getLazyKeyBaseFromDir(rootDir string) (cosmosKeys.Keybase, error) {
|
||||||
|
return emintKeys.New(defaultKeyDBName, filepath.Join(rootDir, defaultKeyDBName)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPassphrase returns a passphrase for a given name. It will first retrieve
|
||||||
|
// the key info for that name if the type is local, it'll fetch input from
|
||||||
|
// STDIN. Otherwise, an empty passphrase is returned. An error is returned if
|
||||||
|
// the key info cannot be fetched or reading from STDIN fails.
|
||||||
|
func GetPassphrase(name string) (string, error) {
|
||||||
|
var passphrase string
|
||||||
|
|
||||||
|
keyInfo, err := GetKeyInfo(name)
|
||||||
|
if err != nil {
|
||||||
|
return passphrase, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only need a passphrase for locally stored keys
|
||||||
|
if keyInfo.GetType().String() == emintKeys.TypeLocal.String() {
|
||||||
|
passphrase, err = clientkeys.ReadPassphraseFromStdin(name)
|
||||||
|
if err != nil {
|
||||||
|
return passphrase, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return passphrase, nil
|
||||||
|
}
|
||||||
|
|
||||||
func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) {
|
func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) {
|
||||||
ko, err := bechKeyOut(keyInfo)
|
ko, err := bechKeyOut(keyInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -39,7 +85,7 @@ func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) {
|
|||||||
|
|
||||||
switch viper.Get(cli.OutputFlag) {
|
switch viper.Get(cli.OutputFlag) {
|
||||||
case OutputFormatText:
|
case OutputFormatText:
|
||||||
printTextInfos([]cosmosKeys.KeyOutput{ko})
|
printTextInfos([]emintKeys.KeyOutput{ko})
|
||||||
|
|
||||||
case OutputFormatJSON:
|
case OutputFormatJSON:
|
||||||
var out []byte
|
var out []byte
|
||||||
@ -84,7 +130,7 @@ func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func printTextInfos(kos []cosmosKeys.KeyOutput) {
|
func printTextInfos(kos []emintKeys.KeyOutput) {
|
||||||
out, err := yaml.Marshal(&kos)
|
out, err := yaml.Marshal(&kos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
135
x/evm/client/cli/tx.go
Normal file
135
x/evm/client/cli/tx.go
Normal file
@ -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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
348
x/evm/client/utils/tx.go
Normal file
348
x/evm/client/utils/tx.go
Normal file
@ -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
|
||||||
|
}
|
@ -55,7 +55,7 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
|
|||||||
|
|
||||||
// Get the root tx command of this module
|
// Get the root tx command of this module
|
||||||
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||||
return nil // cli.GetTxCmd(StoreKey, cdc)
|
return cli.GetTxCmd(types.ModuleName, cdc)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppModule struct {
|
type AppModule struct {
|
||||||
|
@ -28,7 +28,7 @@ var big8 = big.NewInt(8)
|
|||||||
// message type and route constants
|
// message type and route constants
|
||||||
const (
|
const (
|
||||||
TypeEthereumTxMsg = "ethereum_tx"
|
TypeEthereumTxMsg = "ethereum_tx"
|
||||||
RouteEthereumTxMsg = "evm"
|
RouteEthereumTxMsg = RouterKey
|
||||||
)
|
)
|
||||||
|
|
||||||
// EthereumTxMsg encapsulates an Ethereum transaction as an SDK message.
|
// 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.
|
// checks of a Transaction. If returns an sdk.Error if validation fails.
|
||||||
func (msg EthereumTxMsg) ValidateBasic() sdk.Error {
|
func (msg EthereumTxMsg) ValidateBasic() sdk.Error {
|
||||||
if msg.Data.Price.Sign() != 1 {
|
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 {
|
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
|
return nil
|
||||||
|
153
x/evm/types/msg_encoding.go
Normal file
153
x/evm/types/msg_encoding.go
Normal file
@ -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
|
@ -122,7 +122,11 @@ func TestMsgEthereumTxSig(t *testing.T) {
|
|||||||
|
|
||||||
func TestMsgEthereumTxAmino(t *testing.T) {
|
func TestMsgEthereumTxAmino(t *testing.T) {
|
||||||
addr := GenerateEthAddress()
|
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)
|
raw, err := ModuleCdc.MarshalBinaryBare(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -133,3 +137,39 @@ func TestMsgEthereumTxAmino(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, msg.Data, msg2.Data)
|
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)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user