diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000000000000000000000000000000000000..d131f510b8af05ee34f0566500bf377e97b1cceb --- /dev/null +++ b/client/client.go @@ -0,0 +1,82 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package client + +import ( + "fmt" + + "github.com/tendermint/go-rpc/client" + + "github.com/eris-ltd/eris-db/account" + tendermint_client "github.com/eris-ltd/eris-db/rpc/tendermint/client" + "github.com/eris-ltd/eris-db/txs" +) + +type NodeClient interface { + Broadcast(transaction txs.Tx) (*txs.Receipt, error) + + GetAccount(address []byte) (*account.Account, error) +} + +// NOTE [ben] Compiler check to ensure ErisClient successfully implements +// eris-db/client.NodeClient +var _ NodeClient = (*ErisNodeClient)(nil) + +// Eris-Client is a simple struct exposing the client rpc methods + +type ErisNodeClient struct { + broadcastRPC string +} + +// ErisKeyClient.New returns a new eris-keys client for provided rpc location +// Eris-keys connects over http request-responses +func NewErisNodeClient(rpcString string) *ErisNodeClient { + return &ErisNodeClient{ + broadcastRPC: rpcString, + } +} + +//------------------------------------------------------------------------------------ +// broadcast to blockchain node +// NOTE: [ben] Eris Client first continues from tendermint rpc, but will have handshake to negotiate +// protocol version for moving towards rpc/v1 + +func (erisClient *ErisNodeClient) Broadcast(tx txs.Tx) (*txs.Receipt, error) { + client := rpcclient.NewClientURI(erisClient.broadcastRPC) + receipt, err := tendermint_client.BroadcastTx(client, tx) + if err != nil { + return nil, err + } + return &receipt, nil +} + +func (erisClient *ErisNodeClient) GetAccount(address []byte) (*account.Account, error) { + // fetch nonce from node + client := rpcclient.NewClientURI(erisClient.broadcastRPC) + account, err := tendermint_client.GetAccount(client, address) + if err != nil { + err = fmt.Errorf("Error connecting to node (%s) to fetch account (%X): %s", + erisClient.broadcastRPC, address, err.Error()) + return nil, err + } + if account == nil { + err = fmt.Errorf("Unknown account %X at node (%s)", address, erisClient.broadcastRPC) + return nil, err + } + + return account.Copy(), nil +} diff --git a/client/cmd/eris-client.go b/client/cmd/eris-client.go new file mode 100644 index 0000000000000000000000000000000000000000..9176ca0a272daf6ec7105eb22cdf2a654b50d69b --- /dev/null +++ b/client/cmd/eris-client.go @@ -0,0 +1,118 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package commands + +import ( + "os" + "strconv" + "strings" + + "github.com/spf13/cobra" + + log "github.com/eris-ltd/eris-logger" + + "github.com/eris-ltd/eris-db/definitions" + "github.com/eris-ltd/eris-db/version" +) + +// Global flags for persistent flags +var clientDo *definitions.ClientDo + +var ErisClientCmd = &cobra.Command{ + Use: "eris-client", + Short: "Eris-client interacts with a running Eris chain.", + Long: `Eris-client interacts with a running Eris chain. + +Made with <3 by Eris Industries. + +Complete documentation is available at https://docs.erisindustries.com +` + "\nVERSION:\n " + version.VERSION, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + log.SetLevel(log.WarnLevel) + if clientDo.Verbose { + log.SetLevel(log.InfoLevel) + } else if clientDo.Debug { + log.SetLevel(log.DebugLevel) + } + }, + Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, +} + +func Execute() { + InitErisClientInit() + AddGlobalFlags() + AddClientCommands() + ErisClientCmd.Execute() +} + +func InitErisClientInit() { + // initialise an empty ClientDo struct for command execution + clientDo = definitions.NewClientDo() +} + +func AddGlobalFlags() { + ErisClientCmd.PersistentFlags().BoolVarP(&clientDo.Verbose, "verbose", "v", defaultVerbose(), "verbose output; more output than no output flags; less output than debug level; default respects $ERIS_CLIENT_VERBOSE") + ErisClientCmd.PersistentFlags().BoolVarP(&clientDo.Debug, "debug", "d", defaultDebug(), "debug level output; the most output available for eris-client; if it is too chatty use verbose flag; default respects $ERIS_CLIENT_DEBUG") +} + +func AddClientCommands() { + buildTransactionCommand() + ErisClientCmd.AddCommand(TransactionCmd) +} + +//------------------------------------------------------------------------------ +// Defaults + +// defaultVerbose is set to false unless the ERIS_CLIENT_VERBOSE environment +// variable is set to a parsable boolean. +func defaultVerbose() bool { + return setDefaultBool("ERIS_CLIENT_VERBOSE", false) +} + +// defaultDebug is set to false unless the ERIS_CLIENT_DEBUG environment +// variable is set to a parsable boolean. +func defaultDebug() bool { + return setDefaultBool("ERIS_CLIENT_DEBUG", false) +} + +// setDefaultBool returns the provided default value if the environment variable +// is not set or not parsable as a bool. +func setDefaultBool(environmentVariable string, defaultValue bool) bool { + value := os.Getenv(environmentVariable) + if value != "" { + if parsedValue, err := strconv.ParseBool(value); err == nil { + return parsedValue + } + } + return defaultValue +} + +func setDefaultString(envVar, def string) string { + env := os.Getenv(envVar) + if env != "" { + return env + } + return def +} + +func setDefaultStringSlice(envVar string, def []string) []string { + env := os.Getenv(envVar) + if env != "" { + return strings.Split(env, ",") + } + return def +} diff --git a/client/cmd/eris-client/main.go b/client/cmd/eris-client/main.go new file mode 100644 index 0000000000000000000000000000000000000000..8219af99a32a54afdbeff73b10d13f88cf38f3a3 --- /dev/null +++ b/client/cmd/eris-client/main.go @@ -0,0 +1,25 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package main + +import ( + commands "github.com/eris-ltd/eris-db/client/cmd" +) + +func main() { + commands.Execute() +} diff --git a/client/cmd/transaction.go b/client/cmd/transaction.go new file mode 100644 index 0000000000000000000000000000000000000000..13076b0ec366a21a1e06082de52ff2dbf99f8e17 --- /dev/null +++ b/client/cmd/transaction.go @@ -0,0 +1,205 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package commands + +import ( + "os" + "strings" + + "github.com/spf13/cobra" + + log "github.com/eris-ltd/eris-logger" + + "github.com/eris-ltd/eris-db/client/transaction" +) + +var TransactionCmd = &cobra.Command{ + Use: "tx", + Short: "eris-client tx formulates and signs a transaction to a chain", + Long: `eris-client tx formulates and signs a transaction to a chain. +`, + Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, +} + +func buildTransactionCommand() { + // Transaction command has subcommands send, name, call, bond, + // unbond, rebond, permissions. Dupeout transaction is not accessible through the command line. + + addTransactionPersistentFlags() + + // SendTx + var sendCmd = &cobra.Command{ + Use: "send", + Short: "eris-client tx send --amt <amt> --to <addr>", + Long: "eris-client tx send --amt <amt> --to <addr>", + Run: func(cmd *cobra.Command, args []string) { + transaction.Send(clientDo) + }, + PreRun: assertParameters, + } + sendCmd.Flags().StringVarP(&clientDo.AmtFlag, "amt", "a", "", "specify an amount") + sendCmd.Flags().StringVarP(&clientDo.ToFlag, "to", "t", "", "specify an address to send to") + + // NameTx + var nameCmd = &cobra.Command{ + Use: "name", + Short: "eris-client tx name --amt <amt> --name <name> --data <data>", + Long: "eris-client tx name --amt <amt> --name <name> --data <data>", + Run: func(cmd *cobra.Command, args []string) { + // transaction.Name(clientDo) + }, + PreRun: assertParameters, + } + nameCmd.Flags().StringVarP(&clientDo.AmtFlag, "amt", "a", "", "specify an amount") + nameCmd.Flags().StringVarP(&clientDo.NameFlag, "name", "n", "", "specify a name") + nameCmd.Flags().StringVarP(&clientDo.DataFlag, "data", "", "", "specify some data") + nameCmd.Flags().StringVarP(&clientDo.DataFileFlag, "data-file", "", "", "specify a file with some data") + nameCmd.Flags().StringVarP(&clientDo.FeeFlag, "fee", "f", "", "specify the fee to send") + + // CallTx + var callCmd = &cobra.Command{ + Use: "call", + Short: "eris-client tx call --amt <amt> --fee <fee> --gas <gas> --to <contract addr> --data <data>", + Long: "eris-client tx call --amt <amt> --fee <fee> --gas <gas> --to <contract addr> --data <data>", + Run: func(cmd *cobra.Command, args []string) { + transaction.Call(clientDo) + }, + PreRun: assertParameters, + } + callCmd.Flags().StringVarP(&clientDo.AmtFlag, "amt", "a", "", "specify an amount") + callCmd.Flags().StringVarP(&clientDo.ToFlag, "to", "t", "", "specify an address to send to") + callCmd.Flags().StringVarP(&clientDo.DataFlag, "data", "", "", "specify some data") + callCmd.Flags().StringVarP(&clientDo.FeeFlag, "fee", "f", "", "specify the fee to send") + callCmd.Flags().StringVarP(&clientDo.GasFlag, "gas", "g", "", "specify the gas limit for a CallTx") + + // BondTx + var bondCmd = &cobra.Command{ + Use: "bond", + Short: "eris-client tx bond --pubkey <pubkey> --amt <amt> --unbond-to <address>", + Long: "eris-client tx bond --pubkey <pubkey> --amt <amt> --unbond-to <address>", + Run: func(cmd *cobra.Command, args []string) { + // transaction.Bond(clientDo) + }, + PreRun: assertParameters, + } + bondCmd.Flags().StringVarP(&clientDo.AmtFlag, "amt", "a", "", "specify an amount") + bondCmd.Flags().StringVarP(&clientDo.UnbondtoFlag, "to", "t", "", "specify an address to unbond to") + + // UnbondTx + var unbondCmd = &cobra.Command{ + Use: "unbond", + Short: "eris-client tx unbond --addr <address> --height <block_height>", + Long: "eris-client tx unbond --addr <address> --height <block_height>", + Run: func(cmd *cobra.Command, args []string) { + // transaction.Unbond(clientDo) + }, + PreRun: assertParameters, + } + unbondCmd.Flags().StringVarP(&clientDo.AddrFlag, "addr", "a", "", "specify an address") + unbondCmd.Flags().StringVarP(&clientDo.HeightFlag, "height", "n", "", "specify a height to unbond at") + + // RebondTx + var rebondCmd = &cobra.Command{ + Use: "rebond", + Short: "eris-client tx rebond --addr <address> --height <block_height>", + Long: "eris-client tx rebond --addr <address> --height <block_height>", + Run: func(cmd *cobra.Command, args []string) { + // transaction.Rebond(clientDo) + }, + PreRun: assertParameters, + } + rebondCmd.Flags().StringVarP(&clientDo.AddrFlag, "addr", "a", "", "specify an address") + rebondCmd.Flags().StringVarP(&clientDo.HeightFlag, "height", "n", "", "specify a height to unbond at") + + // PermissionsTx + var permissionsCmd = &cobra.Command{ + Use: "permission", + Short: "eris-client tx perm <function name> <args ...>", + Long: "eris-client tx perm <function name> <args ...>", + Run: func(cmd *cobra.Command, args []string) { + // transaction.Permsissions(clientDo) + }, + PreRun: assertParameters, + } + + TransactionCmd.AddCommand(sendCmd, nameCmd, callCmd, bondCmd, unbondCmd, rebondCmd, permissionsCmd) +} + +func addTransactionPersistentFlags() { + TransactionCmd.PersistentFlags().StringVarP(&clientDo.SignAddrFlag, "sign-addr", "", defaultKeyDaemonAddress(), "set eris-keys daemon address (default respects $ERIS_CLIENT_SIGN_ADDRESS)") + TransactionCmd.PersistentFlags().StringVarP(&clientDo.NodeAddrFlag, "node-addr", "", defaultNodeRpcAddress(), "set the eris-db node rpc server address (default respects $ERIS_CLIENT_NODE_ADDRESS)") + TransactionCmd.PersistentFlags().StringVarP(&clientDo.PubkeyFlag, "pubkey", "", defaultPublicKey(), "specify the public key to sign with (defaults to $ERIS_CLIENT_PUBLIC_KEY)") + TransactionCmd.PersistentFlags().StringVarP(&clientDo.AddrFlag, "addr", "", defaultAddress(), "specify the account address (for which the public key can be found at eris-keys) (default respects $ERIS_CLIENT_ADDRESS)") + TransactionCmd.PersistentFlags().StringVarP(&clientDo.ChainidFlag, "chain-id", "", defaultChainId(), "specify the chainID (default respects $CHAIN_ID)") + TransactionCmd.PersistentFlags().StringVarP(&clientDo.NonceFlag, "nonce", "", "", "specify the nonce to use for the transaction (should equal the sender account's nonce + 1)") + + // TransactionCmd.PersistentFlags().BoolVarP(&clientDo.SignFlag, "sign", "s", false, "sign the transaction using the eris-keys daemon") + TransactionCmd.PersistentFlags().BoolVarP(&clientDo.BroadcastFlag, "broadcast", "b", true, "broadcast the transaction to the blockchain") + TransactionCmd.PersistentFlags().BoolVarP(&clientDo.WaitFlag, "wait", "w", false, "wait for the transaction to be committed in a block") +} + +//------------------------------------------------------------------------------ +// Defaults + +func defaultChainId() string { + return setDefaultString("CHAIN_ID", "") +} + +func defaultKeyDaemonAddress() string { + return setDefaultString("ERIS_CLIENT_SIGN_ADDRESS", "http://127.0.0.1:4767") +} + +func defaultNodeRpcAddress() string { + return setDefaultString("ERIS_CLIENT_NODE_ADDRESS", "tcp://127.0.0.1:46657") +} + +func defaultPublicKey() string { + return setDefaultString("ERIS_CLIENT_PUBLIC_KEY", "") +} + +func defaultAddress() string { + return setDefaultString("ERIS_CLIENT_ADDRESS", "") +} + +//------------------------------------------------------------------------------ +// Helper functions + +func assertParameters(cmd *cobra.Command, args []string) { + if clientDo.ChainidFlag == "" { + log.Fatal(`Please provide a chain id either through the flag --chain-id or environment variable $CHAIN_ID.`) + os.Exit(1) + } + + if !strings.HasPrefix(clientDo.NodeAddrFlag, "tcp://") && + !strings.HasPrefix(clientDo.NodeAddrFlag, "unix://") { + // TODO: [ben] go-rpc will deprecate reformatting; also it is bad practice to auto-correct for this; + log.Warn(`Please use fully formed listening address for the node, including the tcp:// or unix:// prefix.`) + } + + if !strings.HasPrefix(clientDo.SignAddrFlag, "http://") { + // NOTE: [ben] we preserve the auto-correction here as it is a simple http request-response to the key server. + clientDo.SignAddrFlag = "http://" + clientDo.SignAddrFlag + log.WithFields(log.Fields{ + "signing address": clientDo.SignAddrFlag, + }).Warn(`Please use fully formed listening address for the key server; adding http:// prefix`) + } + log.WithFields(log.Fields{ + "signing address": clientDo.SignAddrFlag, + "node address": clientDo.NodeAddrFlag, + "chain id": clientDo.ChainidFlag, + }).Debug("Asserted parameters") +} diff --git a/client/core/transaction_factory.go b/client/core/transaction_factory.go new file mode 100644 index 0000000000000000000000000000000000000000..73c6f5c2814b182a9588f960b2504acab1a9d910 --- /dev/null +++ b/client/core/transaction_factory.go @@ -0,0 +1,450 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package core + +import ( + "encoding/hex" + "fmt" + "strconv" + // "strings" + // "time" + + ptypes "github.com/eris-ltd/eris-db/permission/types" + + // log "github.com/eris-ltd/eris-logger" + + "github.com/eris-ltd/eris-db/client" + "github.com/eris-ltd/eris-db/keys" + "github.com/eris-ltd/eris-db/txs" +) + +var ( + MaxCommitWaitTimeSeconds = 20 +) + +//------------------------------------------------------------------------------------ +// core functions with string args. +// validates strings and forms transaction + +func Send(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, toAddr, amtS, nonceS string) (*txs.SendTx, error) { + pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS) + if err != nil { + return nil, err + } + + if toAddr == "" { + return nil, fmt.Errorf("destination address must be given with --to flag") + } + + toAddrBytes, err := hex.DecodeString(toAddr) + if err != nil { + return nil, fmt.Errorf("toAddr is bad hex: %v", err) + } + + tx := txs.NewSendTx() + tx.AddInputWithNonce(pub, amt, int(nonce)) + tx.AddOutput(toAddrBytes, amt) + + return tx, nil +} + +func Call(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, toAddr, amtS, nonceS, gasS, feeS, data string) (*txs.CallTx, error) { + pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS) + if err != nil { + return nil, err + } + + toAddrBytes, err := hex.DecodeString(toAddr) + if err != nil { + return nil, fmt.Errorf("toAddr is bad hex: %v", err) + } + + fee, err := strconv.ParseInt(feeS, 10, 64) + if err != nil { + return nil, fmt.Errorf("fee is misformatted: %v", err) + } + + gas, err := strconv.ParseInt(gasS, 10, 64) + if err != nil { + return nil, fmt.Errorf("gas is misformatted: %v", err) + } + + dataBytes, err := hex.DecodeString(data) + if err != nil { + return nil, fmt.Errorf("data is bad hex: %v", err) + } + + tx := txs.NewCallTxWithNonce(pub, toAddrBytes, dataBytes, amt, gas, fee, int(nonce)) + return tx, nil +} + +func Name(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, amtS, nonceS, feeS, name, data string) (*txs.NameTx, error) { + pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS) + if err != nil { + return nil, err + } + + fee, err := strconv.ParseInt(feeS, 10, 64) + if err != nil { + return nil, fmt.Errorf("fee is misformatted: %v", err) + } + + tx := txs.NewNameTxWithNonce(pub, name, data, amt, fee, int(nonce)) + return tx, nil +} + +type PermFunc struct { + Name string + Args string +} + +var PermsFuncs = []PermFunc{ + {"set_base", "address, permission flag, value"}, + {"unset_base", "address, permission flag"}, + {"set_global", "permission flag, value"}, + {"add_role", "address, role"}, + {"rm_role", "address, role"}, +} + +func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addrS, nonceS, permFunc string, argsS []string) (*txs.PermissionsTx, error) { + pub, _, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addrS, "0", nonceS) + if err != nil { + return nil, err + } + var args ptypes.PermArgs + switch permFunc { + case "set_base": + addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1]) + if err != nil { + return nil, err + } + if len(argsS) != 3 { + return nil, fmt.Errorf("set_base also takes a value (true or false)") + } + var value bool + if argsS[2] == "true" { + value = true + } else if argsS[2] == "false" { + value = false + } else { + return nil, fmt.Errorf("Unknown value %s", argsS[2]) + } + args = &ptypes.SetBaseArgs{addr, pF, value} + case "unset_base": + addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1]) + if err != nil { + return nil, err + } + args = &ptypes.UnsetBaseArgs{addr, pF} + case "set_global": + pF, err := ptypes.PermStringToFlag(argsS[0]) + if err != nil { + return nil, err + } + var value bool + if argsS[1] == "true" { + value = true + } else if argsS[1] == "false" { + value = false + } else { + return nil, fmt.Errorf("Unknown value %s", argsS[1]) + } + args = &ptypes.SetGlobalArgs{pF, value} + case "add_role": + addr, err := hex.DecodeString(argsS[0]) + if err != nil { + return nil, err + } + args = &ptypes.AddRoleArgs{addr, argsS[1]} + case "rm_role": + addr, err := hex.DecodeString(argsS[0]) + if err != nil { + return nil, err + } + args = &ptypes.RmRoleArgs{addr, argsS[1]} + default: + return nil, fmt.Errorf("Invalid permission function for use in PermissionsTx: %s", permFunc) + } + // args := snativeArgs( + tx := txs.NewPermissionsTxWithNonce(pub, args, int(nonce)) + return tx, nil +} + +// type NameGetter struct { +// client cclient.Client +// } + +// func (n NameGetter) GetNameRegEntry(name string) *txs.NameRegEntry { +// entry, err := n.client.GetName(name) +// if err != nil { +// panic(err) +// } +// return entry.Entry +// } + +/* +func coreNewAccount(nodeAddr, pubkey, chainID string) (*types.NewAccountTx, error) { + pub, _, _, err := checkCommon(nodeAddr, pubkey, "", "0", "0") + if err != nil { + return nil, err + } + + client := cclient.NewClient(nodeAddr, "HTTP") + return types.NewNewAccountTx(NameGetter{client}, pub, chainID) +} +*/ + +// func Bond(nodeAddr, signAddr, pubkey, unbondAddr, amtS, nonceS string) (*txs.BondTx, error) { +// pub, amt, nonce, err := checkCommon(nodeAddr, signAddr, pubkey, "", amtS, nonceS) +// if err != nil { +// return nil, err +// } +// var pubKey crypto.PubKeyEd25519 +// var unbondAddrBytes []byte + +// if unbondAddr == "" { +// pkb, _ := hex.DecodeString(pubkey) +// copy(pubKey[:], pkb) +// unbondAddrBytes = pubKey.Address() +// } else { +// unbondAddrBytes, err = hex.DecodeString(unbondAddr) +// if err != nil { +// return nil, fmt.Errorf("unbondAddr is bad hex: %v", err) +// } + +// } + +// tx, err := types.NewBondTx(pub) +// if err != nil { +// return nil, err +// } +// tx.AddInputWithNonce(pub, amt, int(nonce)) +// tx.AddOutput(unbondAddrBytes, amt) + +// return tx, nil +// } + +// func Unbond(addrS, heightS string) (*txs.UnbondTx, error) { +// if addrS == "" { +// return nil, fmt.Errorf("Validator address must be given with --addr flag") +// } + +// addrBytes, err := hex.DecodeString(addrS) +// if err != nil { +// return nil, fmt.Errorf("addr is bad hex: %v", err) +// } + +// height, err := strconv.ParseInt(heightS, 10, 32) +// if err != nil { +// return nil, fmt.Errorf("height is misformatted: %v", err) +// } + +// return &types.UnbondTx{ +// Address: addrBytes, +// Height: int(height), +// }, nil +// } + +// func Rebond(addrS, heightS string) (*txs.RebondTx, error) { +// if addrS == "" { +// return nil, fmt.Errorf("Validator address must be given with --addr flag") +// } + +// addrBytes, err := hex.DecodeString(addrS) +// if err != nil { +// return nil, fmt.Errorf("addr is bad hex: %v", err) +// } + +// height, err := strconv.ParseInt(heightS, 10, 32) +// if err != nil { +// return nil, fmt.Errorf("height is misformatted: %v", err) +// } + +// return &types.RebondTx{ +// Address: addrBytes, +// Height: int(height), +// }, nil +// } + +type TxResult struct { + BlockHash []byte // all txs get in a block + Hash []byte // all txs get a hash + + // only CallTx + Address []byte // only for new contracts + Return []byte + Exception string + + //TODO: make Broadcast() errors more responsive so we + // can differentiate mempool errors from other +} + +// Preserve +func SignAndBroadcast(chainID string, nodeClient client.NodeClient, keyClient keys.KeyClient, tx txs.Tx, sign, broadcast, wait bool) (txResult *TxResult, err error) { + // var inputAddr []byte + if sign { + _, tx, err = signTx(keyClient, chainID, tx) + if err != nil { + return nil, err + } + } + + if broadcast { + // if wait { + // var ch chan Msg + // ch, err = subscribeAndWait(tx, chainID, nodeAddr, inputAddr) + // if err != nil { + // return nil, err + // } else { + // defer func() { + // if err != nil { + // // if broadcast threw an error, just return + // return + // } + // log.WithFields(log.Fields{ + // "", + // }).Debug("Waiting for tx to be committed") + // msg := <-ch + // if msg.Error != nil { + // logger.Infof("Encountered error waiting for event: %v\n", msg.Error) + // err = msg.Error + // } else { + // txResult.BlockHash = msg.BlockHash + // txResult.Return = msg.Value + // txResult.Exception = msg.Exception + // } + // }() + // } + // } + var receipt *txs.Receipt + receipt, err = nodeClient.Broadcast(tx) + if err != nil { + return nil, err + } + txResult = &TxResult{ + Hash: receipt.TxHash, + } + // NOTE: [ben] is this consistent with the Ethereum protocol? It should seem + // reasonable to get this returned from the chain directly. Alternatively, + // the benefit is that the we don't need to trust the chain node + if tx_, ok := tx.(*txs.CallTx); ok { + if len(tx_.Address) == 0 { + txResult.Address = txs.NewContractAddress(tx_.Input.Address, tx_.Input.Sequence) + } + } + } + return +} + +//------------------------------------------------------------------------------------ +// wait for events + +type Msg struct { + BlockHash []byte + Value []byte + Exception string + Error error +} + +// func subscribeAndWait(tx txs.Tx, chainID, nodeAddr string, inputAddr []byte) (chan Msg, error) { +// // subscribe to event and wait for tx to be committed +// var wsAddr string +// if strings.HasPrefix(nodeAddr, "http://") { +// wsAddr = strings.TrimPrefix(nodeAddr, "http://") +// } +// if strings.HasPrefix(nodeAddr, "tcp://") { +// wsAddr = strings.TrimPrefix(nodeAddr, "tcp://") +// } +// if strings.HasPrefix(nodeAddr, "unix://") { +// log.WithFields(log.Fields{ +// "node address": nodeAddr, +// }).Warn("Unable to subscribe to websocket from unix socket.") +// return nil, fmt.Errorf("Unable to subscribe to websocket from unix socket: %s", nodeAddr) +// } +// wsAddr = "ws://" + wsAddr +// log.WithFields(log.Fields{ +// "websocket address": wsAddr, +// "endpoint": "/websocket", +// }).Debug("Subscribing to websocket address") +// wsClient := rpcclient.NewWSClient(wsAddr, "/websocket") +// wsClient.Start() +// eid := txs.EventStringAccInput(inputAddr) +// if err := wsClient.Subscribe(eid); err != nil { +// return nil, fmt.Errorf("Error subscribing to AccInput event: %v", err) +// } +// if err := wsClient.Subscribe(txs.EventStringNewBlock()); err != nil { +// return nil, fmt.Errorf("Error subscribing to NewBlock event: %v", err) +// } + +// resultChan := make(chan Msg, 1) + +// var latestBlockHash []byte + +// // Read message +// go func() { +// for { +// result := <-wsClient.EventsCh +// // if its a block, remember the block hash +// blockData, ok := result.Data.(txs.EventDataNewBlock) +// if ok { +// log.Infoln(blockData.Block) +// latestBlockHash = blockData.Block.Hash() +// continue +// } + +// // we don't accept events unless they came after a new block (ie. in) +// if latestBlockHash == nil { +// continue +// } + +// if result.Event != eid { +// logger.Debugf("received unsolicited event! Got %s, expected %s\n", result.Event, eid) +// continue +// } + +// data, ok := result.Data.(types.EventDataTx) +// if !ok { +// resultChan <- Msg{Error: fmt.Errorf("response error: expected result.Data to be *types.EventDataTx")} +// return +// } + +// if !bytes.Equal(types.TxID(chainID, data.Tx), types.TxID(chainID, tx)) { +// logger.Debugf("Received event for same input from another transaction: %X\n", types.TxID(chainID, data.Tx)) +// continue +// } + +// if data.Exception != "" { +// resultChan <- Msg{BlockHash: latestBlockHash, Value: data.Return, Exception: data.Exception} +// return +// } + +// // GOOD! +// resultChan <- Msg{BlockHash: latestBlockHash, Value: data.Return} +// return +// } +// }() + +// // txs should take no more than 10 seconds +// timeoutTicker := time.Tick(time.Duration(MaxCommitWaitTimeSeconds) * time.Second) + +// go func() { +// <-timeoutTicker +// resultChan <- Msg{Error: fmt.Errorf("timed out waiting for event")} +// return +// }() +// return resultChan, nil +// } diff --git a/client/core/transaction_factory_test.go b/client/core/transaction_factory_test.go new file mode 100644 index 0000000000000000000000000000000000000000..aa492b986d1bb70a525422b1d83f944118183b0c --- /dev/null +++ b/client/core/transaction_factory_test.go @@ -0,0 +1,153 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package core + +import ( + "fmt" + "testing" + + mockclient "github.com/eris-ltd/eris-db/client/mock" + mockkeys "github.com/eris-ltd/eris-db/keys/mock" +) + +func TestTransactionFactory(t *testing.T) { + mockKeyClient := mockkeys.NewMockKeyClient() + mockNodeClient := mockclient.NewMockNodeClient() + testTransactionFactorySend(t, mockNodeClient, mockKeyClient) + testTransactionFactoryCall(t, mockNodeClient, mockKeyClient) + testTransactionFactoryName(t, mockNodeClient, mockKeyClient) + testTransactionFactoryPermissions(t, mockNodeClient, mockKeyClient) + // t.Run("BondTransaction", ) + // t.Run("UnbondTransaction", ) + // t.Run("RebondTransaction", ) +} + +func testTransactionFactorySend(t *testing.T, + nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) { + + // generate an ED25519 key and ripemd160 address + addressString := fmt.Sprintf("%X", keyClient.NewKey()) + // Public key can be queried from mockKeyClient.PublicKey(address) + // but here we let the transaction factory retrieve the public key + // which will then also overwrite the address we provide the function. + // As a result we will assert whether address generated above, is identical + // to address in generated transation. + publicKeyString := "" + // generate an additional address to send amount to + toAddressString := fmt.Sprintf("%X", keyClient.NewKey()) + // set an amount to transfer + amountString := "1000" + // unset nonce so that we retrieve nonce from account + nonceString := "" + + _, err := Send(nodeClient, keyClient, publicKeyString, addressString, + toAddressString, amountString, nonceString) + if err != nil { + t.Logf("Error in SendTx: %s", err) + t.Fail() + } + // TODO: test content of Transaction +} + +func testTransactionFactoryCall(t *testing.T, + nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) { + + // generate an ED25519 key and ripemd160 address + addressString := fmt.Sprintf("%X", keyClient.NewKey()) + // Public key can be queried from mockKeyClient.PublicKey(address) + // but here we let the transaction factory retrieve the public key + // which will then also overwrite the address we provide the function. + // As a result we will assert whether address generated above, is identical + // to address in generated transation. + publicKeyString := "" + // generate an additional address to send amount to + toAddressString := fmt.Sprintf("%X", keyClient.NewKey()) + // set an amount to transfer + amountString := "1000" + // unset nonce so that we retrieve nonce from account + nonceString := "" + // set gas + gasString := "1000" + // set fee + feeString := "100" + // set data + dataString := fmt.Sprintf("%X", "We are DOUG.") + + _, err := Call(nodeClient, keyClient, publicKeyString, addressString, + toAddressString, amountString, nonceString, gasString, feeString, dataString) + if err != nil { + t.Logf("Error in CallTx: %s", err) + t.Fail() + } + // TODO: test content of Transaction +} + +func testTransactionFactoryName(t *testing.T, + nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) { + + // generate an ED25519 key and ripemd160 address + addressString := fmt.Sprintf("%X", keyClient.NewKey()) + // Public key can be queried from mockKeyClient.PublicKey(address) + // but here we let the transaction factory retrieve the public key + // which will then also overwrite the address we provide the function. + // As a result we will assert whether address generated above, is identical + // to address in generated transation. + publicKeyString := "" + // set an amount to transfer + amountString := "1000" + // unset nonce so that we retrieve nonce from account + nonceString := "" + // set fee + feeString := "100" + // set data + dataString := fmt.Sprintf("%X", "We are DOUG.") + // set name + nameString := fmt.Sprintf("%s", "DOUG") + + _, err := Name(nodeClient, keyClient, publicKeyString, addressString, + amountString, nonceString, feeString, nameString, dataString) + if err != nil { + t.Logf("Error in NameTx: %s", err) + t.Fail() + } + // TODO: test content of Transaction +} + +func testTransactionFactoryPermissions(t *testing.T, + nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) { + + // generate an ED25519 key and ripemd160 address + addressString := fmt.Sprintf("%X", keyClient.NewKey()) + // Public key can be queried from mockKeyClient.PublicKey(address) + // but here we let the transaction factory retrieve the public key + // which will then also overwrite the address we provide the function. + // As a result we will assert whether address generated above, is identical + // to address in generated transation. + publicKeyString := "" + // generate an additional address to set permissions for + permAddressString := fmt.Sprintf("%X", keyClient.NewKey()) + // unset nonce so that we retrieve nonce from account + nonceString := "" + + _, err := Permissions(nodeClient, keyClient, publicKeyString, addressString, + nonceString, "set_base", []string{permAddressString, "root", "true"}) + if err != nil { + t.Logf("Error in PermissionsTx: %s", err) + t.Fail() + } + // TODO: test content of Transaction +} \ No newline at end of file diff --git a/client/core/transaction_factory_util.go b/client/core/transaction_factory_util.go new file mode 100644 index 0000000000000000000000000000000000000000..b5ed8250b4fc3affe355c85ac4d799c5f0086af6 --- /dev/null +++ b/client/core/transaction_factory_util.go @@ -0,0 +1,173 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package core + +import ( + "encoding/hex" + "fmt" + "strconv" + + log "github.com/eris-ltd/eris-logger" + + "github.com/tendermint/go-crypto" + + acc "github.com/eris-ltd/eris-db/account" + "github.com/eris-ltd/eris-db/client" + "github.com/eris-ltd/eris-db/keys" + ptypes "github.com/eris-ltd/eris-db/permission/types" + "github.com/eris-ltd/eris-db/txs" +) + +//------------------------------------------------------------------------------------ +// sign and broadcast convenience + +// tx has either one input or we default to the first one (ie for send/bond) +// TODO: better support for multisig and bonding +func signTx(keyClient keys.KeyClient, chainID string, tx_ txs.Tx) ([]byte, txs.Tx, error) { + signBytes := []byte(fmt.Sprintf("%X", acc.SignBytes(chainID, tx_))) + var inputAddr []byte + var sigED crypto.SignatureEd25519 + switch tx := tx_.(type) { + case *txs.SendTx: + inputAddr = tx.Inputs[0].Address + defer func(s *crypto.SignatureEd25519) { tx.Inputs[0].Signature = *s }(&sigED) + case *txs.NameTx: + inputAddr = tx.Input.Address + defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) + case *txs.CallTx: + inputAddr = tx.Input.Address + defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) + case *txs.PermissionsTx: + inputAddr = tx.Input.Address + defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) + case *txs.BondTx: + inputAddr = tx.Inputs[0].Address + defer func(s *crypto.SignatureEd25519) { + tx.Signature = *s + tx.Inputs[0].Signature = *s + }(&sigED) + case *txs.UnbondTx: + inputAddr = tx.Address + defer func(s *crypto.SignatureEd25519) { tx.Signature = *s }(&sigED) + case *txs.RebondTx: + inputAddr = tx.Address + defer func(s *crypto.SignatureEd25519) { tx.Signature = *s }(&sigED) + } + sig, err := keyClient.Sign(signBytes, inputAddr) + if err != nil { + return nil, nil, err + } + // TODO: [ben] temporarily address the type conflict here, to be cleaned up + // with full type restructuring + var sig64 [64]byte + copy(sig64[:], sig) + sigED = crypto.SignatureEd25519(sig64) + log.WithFields(log.Fields{ + "transaction sign bytes": fmt.Sprintf("%X", signBytes), + "account address": fmt.Sprintf("%X", inputAddr), + "signature": fmt.Sprintf("%X", sig64), + }).Debug("Signed transaction") + return inputAddr, tx_, nil +} + +func decodeAddressPermFlag(addrS, permFlagS string) (addr []byte, pFlag ptypes.PermFlag, err error) { + if addr, err = hex.DecodeString(addrS); err != nil { + return + } + if pFlag, err = ptypes.PermStringToFlag(permFlagS); err != nil { + return + } + return +} + +func checkCommon(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, amtS, nonceS string) (pub crypto.PubKey, amt int64, nonce int64, err error) { + if amtS == "" { + err = fmt.Errorf("input must specify an amount with the --amt flag") + return + } + + var pubKeyBytes []byte + if pubkey == "" && addr == "" { + err = fmt.Errorf("at least one of --pubkey or --addr must be given") + return + } else if pubkey != "" { + if addr != "" { + log.WithFields(log.Fields{ + "public key": pubkey, + "address": addr, + }).Info("you have specified both a pubkey and an address. the pubkey takes precedent") + } + pubKeyBytes, err = hex.DecodeString(pubkey) + if err != nil { + err = fmt.Errorf("pubkey is bad hex: %v", err) + return + } + } else { + // grab the pubkey from eris-keys + addressBytes, err2 := hex.DecodeString(addr) + if err2 != nil { + err = fmt.Errorf("Bad hex string for address (%s): %v", addr, err) + return + } + pubKeyBytes, err2 = keyClient.PublicKey(addressBytes) + if err2 != nil { + err = fmt.Errorf("Failed to fetch pubkey for address (%s): %v", addr, err) + return + } + + } + + if len(pubKeyBytes) == 0 { + err = fmt.Errorf("Error resolving public key") + return + } + + amt, err = strconv.ParseInt(amtS, 10, 64) + if err != nil { + err = fmt.Errorf("amt is misformatted: %v", err) + } + + var pubArray [32]byte + copy(pubArray[:], pubKeyBytes) + pub = crypto.PubKeyEd25519(pubArray) + addrBytes := pub.Address() + + if nonceS == "" { + if nodeClient == nil { + err = fmt.Errorf("input must specify a nonce with the --nonce flag or use --node-addr (or ERIS_CLIENT_NODE_ADDR) to fetch the nonce from a node") + return + } + // fetch nonce from node + account, err2 := nodeClient.GetAccount(addrBytes) + if err2 != nil { + return pub, amt, nonce, err2 + } + nonce = int64(account.Sequence) + 1 + log.WithFields(log.Fields{ + "nonce": nonce, + "account address": fmt.Sprintf("%X", addrBytes), + }).Debug("Fetch nonce from node") + } else { + nonce, err = strconv.ParseInt(nonceS, 10, 64) + if err != nil { + err = fmt.Errorf("nonce is misformatted: %v", err) + return + } + } + + return +} \ No newline at end of file diff --git a/client/mock/client_mock.go b/client/mock/client_mock.go new file mode 100644 index 0000000000000000000000000000000000000000..0fc7e576753a3d123650e99ea27f0b839a47bdec --- /dev/null +++ b/client/mock/client_mock.go @@ -0,0 +1,70 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package mock + +import ( + "github.com/tendermint/go-crypto" + + acc "github.com/eris-ltd/eris-db/account" + . "github.com/eris-ltd/eris-db/client" + "github.com/eris-ltd/eris-db/txs" +) + +// NOTE [ben] Compiler check to ensure ErisMockClient successfully implements +// eris-db/client.NodeClient +var _ NodeClient = (*MockNodeClient)(nil) + +type MockNodeClient struct { + accounts map[string]*acc.Account +} + +func NewMockNodeClient() *MockNodeClient { + return &MockNodeClient{ + accounts: make(map[string]*acc.Account), + } +} + +func (mock *MockNodeClient) Broadcast(transaction txs.Tx) (*txs.Receipt, error) { + // make zero transaction receipt + txReceipt := &txs.Receipt{ + TxHash: make([]byte, 20, 20), + CreatesContract: 0, + ContractAddr: make([]byte, 20, 20), + } + return txReceipt, nil +} + +func (mock *MockNodeClient) GetAccount(address []byte) (*acc.Account, error) { + // make zero account + var zero [32]byte + copyAddressBytes := make([]byte, len(address), len(address)) + copy(copyAddressBytes, address) + account := &acc.Account{ + Address: copyAddressBytes, + PubKey: crypto.PubKey(crypto.PubKeyEd25519(zero)), + Sequence: 0, + Balance: 0, + Code: make([]byte, 0), + StorageRoot: make([]byte, 0), + } + return account, nil +} + +func (mock *MockNodeClient) MockAddAccount(account *acc.Account) { + addressString := string(account.Address[:]) + mock.accounts[addressString] = account.Copy() +} diff --git a/client/transaction/transaction.go b/client/transaction/transaction.go new file mode 100644 index 0000000000000000000000000000000000000000..e9840d244cacff3bb8b88d90d6dd23bf4eb00974 --- /dev/null +++ b/client/transaction/transaction.go @@ -0,0 +1,96 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package transaction + +import ( + "fmt" + "os" + + log "github.com/eris-ltd/eris-logger" + + "github.com/eris-ltd/eris-db/client" + "github.com/eris-ltd/eris-db/client/core" + "github.com/eris-ltd/eris-db/definitions" + "github.com/eris-ltd/eris-db/keys" +) + +func Send(do *definitions.ClientDo) { + // construct two clients to call out to keys server and + // blockchain node. + erisKeyClient := keys.NewErisKeyClient(do.SignAddrFlag) + erisNodeClient := client.NewErisNodeClient(do.NodeAddrFlag) + // form the send transaction + sendTransaction, err := core.Send(erisNodeClient, erisKeyClient, + do.PubkeyFlag, do.AddrFlag, do.ToFlag, do.AmtFlag, do.NonceFlag) + if err != nil { + log.Fatalf("Failed on forming Send Transaction: %s", err) + return + } + // TODO: [ben] we carry over the sign bool, but always set it to true, + // as we move away from and deprecate the api that allows sending unsigned + // transactions and relying on (our) receiving node to sign it. + unpackSignAndBroadcast( + core.SignAndBroadcast(do.ChainidFlag, erisNodeClient, + erisKeyClient, sendTransaction, true, do.BroadcastFlag, do.WaitFlag)) +} + +func Call(do *definitions.ClientDo) { + // construct two clients to call out to keys server and + // blockchain node. + erisKeyClient := keys.NewErisKeyClient(do.SignAddrFlag) + erisNodeClient := client.NewErisNodeClient(do.NodeAddrFlag) + // form the call transaction + callTransaction, err := core.Call(erisNodeClient, erisKeyClient, + do.PubkeyFlag, do.AddrFlag, do.ToFlag, do.AmtFlag, do.NonceFlag, + do.GasFlag, do.FeeFlag, do.DataFlag) + if err != nil { + log.Fatalf("Failed on forming Call Transaction: %s", err) + return + } + // TODO: [ben] we carry over the sign bool, but always set it to true, + // as we move away from and deprecate the api that allows sending unsigned + // transactions and relying on (our) receiving node to sign it. + unpackSignAndBroadcast( + core.SignAndBroadcast(do.ChainidFlag, erisNodeClient, + erisKeyClient, callTransaction, true, do.BroadcastFlag, do.WaitFlag)) +} + +//---------------------------------------------------------------------- +// Helper functions + +func unpackSignAndBroadcast(result *core.TxResult, err error) { + if err != nil { + log.Fatalf("Failed on signing (and broadcasting) transaction: %s", err) + os.Exit(1) + } + if result == nil { + // if we don't provide --sign or --broadcast + return + } + printResult := log.Fields{ + "transaction hash": fmt.Sprintf("%X", result.Hash), + } + if result.Address != nil { + printResult["Contract Address"] = fmt.Sprintf("%X", result.Address) + } + if result.Return != nil { + printResult["Block Hash"] = fmt.Sprintf("%X", result.BlockHash) + printResult["Return Value"] = fmt.Sprintf("%X", result.Return) + printResult["Exception"] = fmt.Sprintf("%s", result.Exception) + } + log.WithFields(printResult).Warn("Result") +} diff --git a/cmd/eris-db.go b/cmd/eris-db.go index 369623252fae2011232472db1add45e6dde5ae6a..4894bd295d3b01ede143a6b361ac7d670556a7bc 100644 --- a/cmd/eris-db.go +++ b/cmd/eris-db.go @@ -34,8 +34,8 @@ var do *definitions.Do var ErisDbCmd = &cobra.Command{ Use: "eris-db", - Short: "Eris-DB is the heart of the eris chain.", - Long: `Eris-DB is the heart of the eris chain. Eris-DB combines + Short: "Eris-DB is the server side of the eris chain.", + Long: `Eris-DB is the server side of the eris chain. Eris-DB combines a modular consensus engine and application manager to run a chain to suit your needs. @@ -56,15 +56,15 @@ Complete documentation is available at https://docs.erisindustries.com } func Execute() { - InitErisDb() + InitErisDbCli() AddGlobalFlags() AddCommands() ErisDbCmd.Execute() } -func InitErisDb() { - // initialise an empty do struct for command execution - do = definitions.NowDo() +func InitErisDbCli() { + // initialise an empty Do struct for command execution + do = definitions.NewDo() } func AddGlobalFlags() { diff --git a/definitions/client_do.go b/definitions/client_do.go new file mode 100644 index 0000000000000000000000000000000000000000..1c103d9ae70260961397fe9fb4421977388f2afd --- /dev/null +++ b/definitions/client_do.go @@ -0,0 +1,78 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package definitions + +type ClientDo struct { + // Persistent flags not reflected in the configuration files + // only set through command line flags or environment variables + Debug bool // ERIS_DB_DEBUG + Verbose bool // ERIS_DB_VERBOSE + + // Following parameters are global flags for eris-client tx + SignAddrFlag string + NodeAddrFlag string + PubkeyFlag string + AddrFlag string + ChainidFlag string + + // signFlag bool // TODO: remove; unsafe signing without eris-keys + BroadcastFlag bool + WaitFlag bool + + // Following parameters are vary for different Transaction subcommands + // some of these are strings rather than flags because the `core` + // functions have a pure string interface so they work nicely from http + AmtFlag string + NonceFlag string + NameFlag string + DataFlag string + DataFileFlag string + ToFlag string + FeeFlag string + GasFlag string + UnbondtoFlag string + HeightFlag string +} + +func NewClientDo() *ClientDo { + clientDo := new(ClientDo) + clientDo.Debug = false + clientDo.Verbose = false + + clientDo.SignAddrFlag = "" + clientDo.NodeAddrFlag = "" + clientDo.PubkeyFlag = "" + clientDo.AddrFlag = "" + clientDo.ChainidFlag = "" + + // clientDo.signFlag = false + clientDo.BroadcastFlag = false + clientDo.WaitFlag = false + + clientDo.AmtFlag = "" + clientDo.NonceFlag = "" + clientDo.NameFlag = "" + clientDo.DataFlag = "" + clientDo.DataFileFlag = "" + clientDo.ToFlag = "" + clientDo.FeeFlag = "" + clientDo.GasFlag = "" + clientDo.UnbondtoFlag = "" + clientDo.HeightFlag = "" + + return clientDo +} \ No newline at end of file diff --git a/definitions/do.go b/definitions/do.go index 273dc34800250c750b842fd4e23576c3ef124912..f022aabbc2495d91933b0da38cbd39c4f0be4399 100644 --- a/definitions/do.go +++ b/definitions/do.go @@ -53,7 +53,7 @@ type Do struct { // Result string } -func NowDo() *Do { +func NewDo() *Do { do := new(Do) do.Debug = false do.Verbose = false diff --git a/glide.lock b/glide.lock index 57f5ee49216192fb15c66205c11df139555405af..8014bc99f9a0ae8a20de92413fb8fbcbe023c1ae 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 11dc81a6cb059737f9fb51d33ea7d80dd6edfa1f4293f6b4b74507011a536f32 -updated: 2016-06-21T11:22:28.775429287+01:00 +hash: f1f85c5d4b9520217cc6fa9fd7b7e97790e737def9bc08ab45d53d5db729c779 +updated: 2016-09-14T20:54:48.289839938+02:00 imports: - name: github.com/Azure/go-ansiterm version: 388960b655244e76e24c75f48631564eaefade62 @@ -21,14 +21,22 @@ imports: version: d6191e27ad06236eaad65d79e49a08b03b9f8029 - name: github.com/BurntSushi/toml version: f0aeabca5a127c4078abb8c8d64298b147264b55 +- name: github.com/davecgh/go-spew + version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + subpackages: + - spew - name: github.com/docker/docker version: 097d1a2b6186dcf1df8ae6135470871aca309a31 subpackages: + - pkg/ioutils - pkg/term + - pkg/longpath - pkg/system - pkg/term/windows - name: github.com/docker/go-units version: f2d77a61e3c169b43402a0a1e84f06daf29b8190 +- name: github.com/eris-ltd/eris-keys + version: 114ebc77443db9a153692233294e48bc7e184215 - name: github.com/eris-ltd/eris-logger version: ea48a395d6ecc0eccc67a26da9fc7a6106fabb84 - name: github.com/fsnotify/fsnotify @@ -73,6 +81,10 @@ imports: version: d2dd0262208475919e1a362f675cfc0e7c10e905 - name: github.com/naoina/toml version: 751171607256bb66e64c9f0220c00662420c38e9 +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib - name: github.com/Sirupsen/logrus version: f3cfb454f4c209e6668c95216c4744b8fddb2356 - name: github.com/spf13/cast @@ -87,6 +99,8 @@ imports: version: c1ccc378a054ea8d4e38d8c67f6938d4760b53dd - name: github.com/stretchr/testify version: d77da356e56a7428ad25149ca77381849a6a5232 + subpackages: + - assert - name: github.com/syndtr/goleveldb version: fa5b5c78794bc5c18f330361059f871ae8c2b9d6 subpackages: @@ -130,10 +144,10 @@ imports: subpackages: - upnp - name: github.com/tendermint/go-rpc - version: 1d9e89812adc202811b7fb8e9e0837e73adadb43 + version: 479510be0e80dd9e5d6b1f941adad168df0af85f subpackages: - - server - client + - server - types - name: github.com/tendermint/go-wire version: 3b0adbc86ed8425eaed98516165b6788d9f4de7a @@ -147,13 +161,13 @@ imports: - node - proxy - types + - version - consensus - rpc/core/types - blockchain - mempool - rpc/core - state - - version - name: github.com/tendermint/tmsp version: 73e5c3cb7bbee2f9c49792e5a0fcbcab442bf7dc subpackages: @@ -175,13 +189,14 @@ imports: - openpgp/errors - curve25519 - name: golang.org/x/net - version: 7c71ca708c71bcbd0e6c856b01468ee07fe24557 + version: de35ec43e7a9aabd6a9c54d2898220ea7e44de7d subpackages: - http2 - context - netutil - trace - http2/hpack + - idna - lex/httplex - internal/timeseries - name: golang.org/x/sys diff --git a/glide.yaml b/glide.yaml index 179439bd07c88b83fbd7838fc69e337598d89cc6..3d3d1d0d7166243a3d7d130aca7e32b3c4a38d04 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,6 +1,7 @@ package: github.com/eris-ltd/eris-db import: - package: github.com/eris-ltd/eris-logger +- package: github.com/eris-ltd/eris-keys - package: github.com/spf13/cobra - package: github.com/spf13/viper - package: github.com/tendermint/tendermint diff --git a/keys/key_client.go b/keys/key_client.go new file mode 100644 index 0000000000000000000000000000000000000000..0372805a25c1ad3f7d5e8f00e3f88d7d779380de --- /dev/null +++ b/keys/key_client.go @@ -0,0 +1,80 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package keys + +import ( + "encoding/hex" +) + +type KeyClient interface { + // Sign needs to return the signature bytes for given message to sign + // and the address to sign it with. + Sign(signBytes []byte, signAddress []byte) (signature []byte, err error) + // PublicKey needs to return the public key associated with a given address + PublicKey(address []byte) (publicKey []byte, err error) +} + +// NOTE [ben] Compiler check to ensure ErisKeyClient successfully implements +// eris-db/keys.KeyClient +var _ KeyClient = (*ErisKeyClient)(nil) + +type ErisKeyClient struct { + rpcString string +} + +// ErisKeyClient.New returns a new eris-keys client for provided rpc location +// Eris-keys connects over http request-responses +func NewErisKeyClient(rpcString string) *ErisKeyClient { + return &ErisKeyClient{ + rpcString: rpcString, + } +} + +// Eris-keys client Sign requests the signature from ErisKeysClient over rpc for the given +// bytes to be signed and the address to sign them with. +func (erisKeys *ErisKeyClient) Sign(signBytes []byte, signAddress []byte) (signature []byte, err error) { + args := map[string]string{ + "msg": string(signBytes), + "hash": string(signBytes), // TODO:[ben] backwards compatibility + "addr": string(signAddress), + } + sigS, err := RequestResponse(erisKeys.rpcString, "sign", args) + if err != nil { + return + } + sigBytes, err := hex.DecodeString(sigS) + if err != nil { + return + } + copy(signature[:], sigBytes) + return +} + +// Eris-keys client PublicKey requests the public key associated with an address from +// the eris-keys server. +func (erisKeys *ErisKeyClient) PublicKey(address []byte) (publicKey []byte, err error) { + args := map[string]string{ + "addr": string(address), + } + pubS, err := RequestResponse(erisKeys.rpcString, "pub", args) + if err != nil { + return + } + // TODO: [ben] assert that received public key results in + // address + return hex.DecodeString(pubS) +} diff --git a/keys/key_client_util.go b/keys/key_client_util.go new file mode 100644 index 0000000000000000000000000000000000000000..7e3dcbba43d67f732d3ff98213075ccf2973534c --- /dev/null +++ b/keys/key_client_util.go @@ -0,0 +1,91 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +// version provides the current Eris-DB version and a VersionIdentifier +// for the modules to identify their version with. + +package keys + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + log "github.com/eris-ltd/eris-logger" +) + +// Eris-Keys server connects over http request-response structures + +type HTTPResponse struct { + Response string + Error string +} + +func RequestResponse(addr, method string, args map[string]string) (string, error) { + b, err := json.Marshal(args) + if err != nil { + return "", err + } + endpoint := fmt.Sprintf("%s/%s", addr, method) + log.WithFields(log.Fields{ + "key server endpoint": endpoint, + "request body": string(b), + }).Debugf("Sending request body to key server") + req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(b)) + if err != nil { + return "", err + } + req.Header.Add("Content-Type", "application/json") + res, errS, err := requestResponse(req) + if err != nil { + return "", fmt.Errorf("Error calling eris-keys at %s: %s", endpoint, err.Error()) + } + if errS != "" { + return "", fmt.Errorf("Error (string) calling eris-keys at %s: %s", endpoint, errS) + } + log.WithFields(log.Fields{ + "endpoint": endpoint, + "request body": string(b), + "response": res, + }).Debugf("Received response from key server") + return res, nil +} + +func requestResponse(req *http.Request) (string, string, error) { + client := new(http.Client) + resp, err := client.Do(req) + if err != nil { + return "", "", err + } + if resp.StatusCode >= 400 { + return "", "", fmt.Errorf(resp.Status) + } + return unpackResponse(resp) +} + +func unpackResponse(resp *http.Response) (string, string, error) { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", "", err + } + r := new(HTTPResponse) + if err := json.Unmarshal(b, r); err != nil { + return "", "", err + } + return r.Response, r.Error, nil +} diff --git a/keys/mock/key_client_mock.go b/keys/mock/key_client_mock.go new file mode 100644 index 0000000000000000000000000000000000000000..67b2ad9c11e6104873f62614378b8afa50210259 --- /dev/null +++ b/keys/mock/key_client_mock.go @@ -0,0 +1,73 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package mock + +import ( + "fmt" + + // for the mock of key server we explicitly import + // the keys server to ensure the core components are + // compatible with eris-db. + "github.com/eris-ltd/eris-keys/crypto" + + . "github.com/eris-ltd/eris-db/keys" +) + +//--------------------------------------------------------------------- +// Mock client for replacing signing done by eris-keys + +// NOTE [ben] Compiler check to ensure MockKeyClient successfully implements +// eris-db/keys.KeyClient +var _ KeyClient = (*MockKeyClient)(nil) + +type MockKeyClient struct { + knownKeys map[string]*crypto.Key +} + +func NewMockKeyClient() *MockKeyClient { + return &MockKeyClient{ + knownKeys: make(map[string]*crypto.Key), + } +} + +func (mock *MockKeyClient) NewKey() (address []byte) { + // Only tests ED25519 curve and ripemd160. + keyType := crypto.KeyType{crypto.CurveTypeEd25519, + crypto.AddrTypeRipemd160} + key, err := crypto.NewKey(keyType) + if err != nil { + panic(fmt.Sprintf("Mocked key client failed on key generation (%s): %s", keyType.String(), err)) + } + mock.knownKeys[fmt.Sprintf("%X", key.Address)] = key + return key.Address +} + +func (mock *MockKeyClient) Sign(signBytes []byte, signAddress []byte) (signature []byte, err error) { + key := mock.knownKeys[fmt.Sprintf("%X", signAddress)] + if key == nil { + return nil, fmt.Errorf("Unknown address (%X)", signAddress) + } + return key.Sign(signBytes) +} + +func (mock *MockKeyClient) PublicKey(address []byte) (publicKey []byte, err error) { + key := mock.knownKeys[fmt.Sprintf("%X", address)] + if key == nil { + return nil, fmt.Errorf("Unknown address (%X)", address) + } + return key.Pubkey() +} diff --git a/version/version.go b/version/version.go index 521120043705cac24a3090a269625717a411cb9f..d285dbecac9f2028de9896410cebf1caf07d721d 100644 --- a/version/version.go +++ b/version/version.go @@ -129,4 +129,4 @@ func (version *VersionIdentifier) MatchesMinorVersion( // IMPORTANT: Eris-DB version must be on the last line of this file for // the deployment script tests/build_tool.sh to pick up the right label. -const VERSION = "0.12.0-rc3" \ No newline at end of file +const VERSION = "0.12.0-rc3"