From bc45e5c6de3052a4c853387dea0af5cd9207f1f7 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Thu, 26 Feb 2015 13:22:09 +0100 Subject: [PATCH 01/37] Integrate eth_accounts and eth_transact to use new account manager * Add from to eth_transact / xeth.Transact and add static pass in lieu of integrating with native Mist window for user passphrase entry * Make eth_accounts return AccountManager.Accounts() * Add a Generate Key menu item in Mist --- cmd/mist/assets/qml/main.qml | 5 +++++ cmd/mist/bindings.go | 4 ++-- cmd/mist/gui.go | 7 ++++++ cmd/mist/ui_lib.go | 1 + core/types/transaction.go | 8 +++++++ eth/backend.go | 41 +++++++++++++++++++++--------------- javascript/types.go | 4 ++-- rpc/api.go | 30 +++++--------------------- rpc/args.go | 1 + xeth/xeth.go | 40 ++++++++++++++++++++++++----------- 10 files changed, 83 insertions(+), 58 deletions(-) diff --git a/cmd/mist/assets/qml/main.qml b/cmd/mist/assets/qml/main.qml index f9ee6939d..7f72d35f4 100644 --- a/cmd/mist/assets/qml/main.qml +++ b/cmd/mist/assets/qml/main.qml @@ -190,6 +190,11 @@ ApplicationWindow { } } + MenuItem { + text: "Generate key" + shortcut: "Ctrl+k" + onTriggered: gui.generateKey() + } } Menu { diff --git a/cmd/mist/bindings.go b/cmd/mist/bindings.go index f21aa3135..fd89eb7e2 100644 --- a/cmd/mist/bindings.go +++ b/cmd/mist/bindings.go @@ -49,7 +49,7 @@ func (gui *Gui) LogPrint(level logger.LogLevel, msg string) { } */ } -func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (string, error) { +func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (string, error) { var data string if len(recipient) == 0 { code, err := ethutil.Compile(d, false) @@ -61,7 +61,7 @@ func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (string, err data = ethutil.Bytes2Hex(utils.FormatTransactionData(d)) } - return gui.xeth.Transact(recipient, value, gas, gasPrice, data) + return gui.xeth.Transact(from, recipient, value, gas, gasPrice, data) } // functions that allow Gui to implement interface guilogger.LogSystem diff --git a/cmd/mist/gui.go b/cmd/mist/gui.go index 869b689dd..bc6e9ed53 100644 --- a/cmd/mist/gui.go +++ b/cmd/mist/gui.go @@ -175,6 +175,13 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { func (gui *Gui) ImportKey(filePath string) { } +func (gui *Gui) GenerateKey() { + _, err := gui.eth.AccountManager().NewAccount("hurr") + if err != nil { + // TODO: UI feedback? + } +} + func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) { context.SetVar("lib", gui) component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml")) diff --git a/cmd/mist/ui_lib.go b/cmd/mist/ui_lib.go index 098e8fca5..af78f0c10 100644 --- a/cmd/mist/ui_lib.go +++ b/cmd/mist/ui_lib.go @@ -171,6 +171,7 @@ func (self *UiLib) Transact(params map[string]interface{}) (string, error) { object := mapToTxParams(params) return self.XEth.Transact( + object["from"], object["to"], object["value"], object["gas"], diff --git a/core/types/transaction.go b/core/types/transaction.go index 7a1d6104e..7d34c86f4 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -129,6 +129,7 @@ func (tx *Transaction) sender() []byte { return crypto.Sha3(pubkey[1:])[12:] } +// TODO: deprecate after new accounts & key stores are integrated func (tx *Transaction) Sign(privk []byte) error { sig := tx.Signature(privk) @@ -140,6 +141,13 @@ func (tx *Transaction) Sign(privk []byte) error { return nil } +func (tx *Transaction) SetSignatureValues(sig []byte) error { + tx.R = sig[:32] + tx.S = sig[32:64] + tx.V = uint64(sig[64] + 27) + return nil +} + func (tx *Transaction) SignECDSA(key *ecdsa.PrivateKey) error { return tx.Sign(crypto.FromECDSA(key)) } diff --git a/eth/backend.go b/eth/backend.go index 1c711a775..02e7e2746 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/ethereum/ethash" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/blockpool" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" @@ -117,6 +118,7 @@ type Ethereum struct { txPool *core.TxPool chainManager *core.ChainManager blockPool *blockpool.BlockPool + accountManager *accounts.AccountManager whisper *whisper.Whisper net *p2p.Server @@ -176,9 +178,13 @@ func New(config *Config) (*Ethereum, error) { DataDir: config.DataDir, } + // TODO: add config flag and case on plain/protected key store + ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir()) + am := accounts.NewAccountManager(ks, 300000) // keys unlocked for 300s + eth.accountManager = &am + eth.chainManager = core.NewChainManager(db, eth.EventMux()) pow := ethash.New(eth.chainManager) - eth.txPool = core.NewTxPool(eth.EventMux()) eth.blockProcessor = core.NewBlockProcessor(db, pow, eth.txPool, eth.chainManager, eth.EventMux()) eth.chainManager.SetProcessor(eth.blockProcessor) @@ -215,22 +221,23 @@ func New(config *Config) (*Ethereum, error) { return eth, nil } -func (s *Ethereum) KeyManager() *crypto.KeyManager { return s.keyManager } -func (s *Ethereum) Logger() logger.LogSystem { return s.logger } -func (s *Ethereum) Name() string { return s.net.Name } -func (s *Ethereum) ChainManager() *core.ChainManager { return s.chainManager } -func (s *Ethereum) BlockProcessor() *core.BlockProcessor { return s.blockProcessor } -func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } -func (s *Ethereum) BlockPool() *blockpool.BlockPool { return s.blockPool } -func (s *Ethereum) Whisper() *whisper.Whisper { return s.whisper } -func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *Ethereum) Db() ethutil.Database { return s.db } -func (s *Ethereum) Miner() *miner.Miner { return s.miner } -func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) PeerCount() int { return s.net.PeerCount() } -func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } -func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } -func (s *Ethereum) Coinbase() []byte { return nil } // TODO +func (s *Ethereum) KeyManager() *crypto.KeyManager { return s.keyManager } +func (s *Ethereum) Logger() logger.LogSystem { return s.logger } +func (s *Ethereum) Name() string { return s.net.Name } +func (s *Ethereum) AccountManager() *accounts.AccountManager { return s.accountManager } +func (s *Ethereum) ChainManager() *core.ChainManager { return s.chainManager } +func (s *Ethereum) BlockProcessor() *core.BlockProcessor { return s.blockProcessor } +func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } +func (s *Ethereum) BlockPool() *blockpool.BlockPool { return s.blockPool } +func (s *Ethereum) Whisper() *whisper.Whisper { return s.whisper } +func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } +func (s *Ethereum) Db() ethutil.Database { return s.db } +func (s *Ethereum) Miner() *miner.Miner { return s.miner } +func (s *Ethereum) IsListening() bool { return true } // Always listening +func (s *Ethereum) PeerCount() int { return s.net.PeerCount() } +func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } +func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } +func (s *Ethereum) Coinbase() []byte { return nil } // TODO // Start the ethereum func (s *Ethereum) Start() error { diff --git a/javascript/types.go b/javascript/types.go index 77e209d19..e07267c8f 100644 --- a/javascript/types.go +++ b/javascript/types.go @@ -70,8 +70,8 @@ func (self *JSEthereum) GetStateObject(addr string) otto.Value { return self.toVal(&JSStateObject{self.XEth.State().SafeGet(addr), self}) } -func (self *JSEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value { - r, err := self.XEth.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr) +func (self *JSEthereum) Transact(fromStr, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value { + r, err := self.XEth.Transact(fromStr, recipient, valueStr, gasStr, gasPriceStr, dataStr) if err != nil { fmt.Println(err) diff --git a/rpc/api.go b/rpc/api.go index 28024c206..b622945eb 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -252,38 +252,18 @@ func (p *EthereumApi) GetBlock(args *GetBlockArgs, reply *interface{}) error { } func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error { - if len(args.Gas) == 0 { + // TODO: align default values to have the same type, e.g. not depend on + // ethutil.Value conversions later on + if ethutil.Big(args.Gas).Cmp(big.NewInt(0)) == 0 { args.Gas = defaultGas.String() } - if len(args.GasPrice) == 0 { + if ethutil.Big(args.GasPrice).Cmp(big.NewInt(0)) == 0 { args.GasPrice = defaultGasPrice.String() } - // TODO if no_private_key then - //if _, exists := p.register[args.From]; exists { - // p.register[args.From] = append(p.register[args.From], args) - //} else { - /* - account := accounts.Get(fromHex(args.From)) - if account != nil { - if account.Unlocked() { - if !unlockAccount(account) { - return - } - } - - result, _ := account.Transact(fromHex(args.To), fromHex(args.Value), fromHex(args.Gas), fromHex(args.GasPrice), fromHex(args.Data)) - if len(result) > 0 { - *reply = toHex(result) - } - } else if _, exists := p.register[args.From]; exists { - p.register[ags.From] = append(p.register[args.From], args) - } - */ - result, _ := p.xeth().Transact( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data) + result, _ := p.xeth().Transact(args.From, args.To, args.Value, args.Gas, args.GasPrice, args.Data) *reply = result - //} return nil } diff --git a/rpc/args.go b/rpc/args.go index ea8489585..ec3359a4a 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -24,6 +24,7 @@ func (obj *GetBlockArgs) UnmarshalJSON(b []byte) (err error) { type NewTxArgs struct { From string `json:"from"` + Pass string `json:"pass"` To string `json:"to"` Value string `json:"value"` Gas string `json:"gas"` diff --git a/xeth/xeth.go b/xeth/xeth.go index 677d40fd5..91bd35f8e 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -7,8 +7,8 @@ package xeth import ( "bytes" "encoding/json" - "fmt" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -27,6 +27,7 @@ var pipelogger = logger.NewLogger("XETH") type Backend interface { BlockProcessor() *core.BlockProcessor ChainManager() *core.ChainManager + AccountManager() *accounts.AccountManager TxPool() *core.TxPool PeerCount() int IsListening() bool @@ -42,6 +43,7 @@ type XEth struct { eth Backend blockProcessor *core.BlockProcessor chainManager *core.ChainManager + accountManager *accounts.AccountManager state *State whisper *Whisper miner *miner.Miner @@ -52,6 +54,7 @@ func New(eth Backend) *XEth { eth: eth, blockProcessor: eth.BlockProcessor(), chainManager: eth.ChainManager(), + accountManager: eth.AccountManager(), whisper: NewWhisper(eth.Whisper()), miner: eth.Miner(), } @@ -106,7 +109,13 @@ func (self *XEth) Block(v interface{}) *Block { } func (self *XEth) Accounts() []string { - return []string{toHex(self.eth.KeyManager().Address())} + // TODO: check err? + accounts, _ := self.eth.AccountManager().Accounts() + accountAddresses := make([]string, len(accounts)) + for i, ac := range accounts { + accountAddresses[i] = toHex(ac.Address) + } + return accountAddresses } func (self *XEth) PeerCount() int { @@ -266,17 +275,19 @@ func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (st return toHex(res), nil } -func (self *XEth) Transact(toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { +func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { + var ( + from []byte to []byte value = ethutil.NewValue(valueStr) gas = ethutil.NewValue(gasStr) price = ethutil.NewValue(gasPriceStr) data []byte - key = self.eth.KeyManager().KeyPair() contractCreation bool ) + from = fromHex(fromStr) data = fromHex(codeStr) to = fromHex(toStr) if len(to) == 0 { @@ -290,21 +301,26 @@ func (self *XEth) Transact(toStr, valueStr, gasStr, gasPriceStr, codeStr string) tx = types.NewTransactionMessage(to, value.BigInt(), gas.BigInt(), price.BigInt(), data) } - var err error - state := self.eth.ChainManager().TxState() - if balance := state.GetBalance(key.Address()); balance.Cmp(tx.Value()) < 0 { - return "", fmt.Errorf("insufficient balance. balance=%v tx=%v", balance, tx.Value()) - } - nonce := state.GetNonce(key.Address()) + state := self.chainManager.TransState() + nonce := state.GetNonce(from) tx.SetNonce(nonce) - tx.Sign(key.PrivateKey) + sig, err := self.accountManager.Sign(&accounts.Account{Address: from}, tx.Hash()) + if err != nil { + return "", err + } + tx.SetSignatureValues(sig) err = self.eth.TxPool().Add(tx) if err != nil { return "", err } - state.SetNonce(key.Address(), nonce+1) + state.SetNonce(from, nonce+1) + + if contractCreation { + addr := core.AddressFromMessage(tx) + pipelogger.Infof("Contract addr %x\n", addr) + } if types.IsContractAddr(to) { return toHex(core.AddressFromMessage(tx)), nil From d66f93cecdbae6a88bfb710e0d95d62340bf2460 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sat, 7 Mar 2015 12:38:33 +0100 Subject: [PATCH 02/37] accounts, core, eth, xeth: use account manager for everything The account manager is now responsible for picking the default account and the coinbase. --- accounts/account_manager.go | 39 ++++++++++++++++++++++++------ core/manager.go | 2 -- eth/backend.go | 47 +++++++++++-------------------------- xeth/xeth.go | 18 +++++++++----- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 3e9fa7799..3b7785231 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -42,7 +42,10 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -var ErrLocked = errors.New("account is locked; please request passphrase") +var ( + ErrLocked = errors.New("account is locked") + ErrNoKeys = errors.New("no keys in store") +) // TODO: better name for this struct? type Account struct { @@ -56,17 +59,39 @@ type AccountManager struct { mutex sync.RWMutex } -func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) AccountManager { - keysMap := make(map[string]crypto.Key) - am := &AccountManager{ +func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) *AccountManager { + return &AccountManager{ keyStore: keyStore, - unlockedKeys: keysMap, + unlockedKeys: make(map[string]crypto.Key), unlockMilliseconds: unlockMilliseconds, } - return *am } -func (am AccountManager) DeleteAccount(address []byte, auth string) error { +// Coinbase returns the account address that mining rewards are sent to. +func (am *AccountManager) Coinbase() (addr []byte, err error) { + // TODO: persist coinbase address on disk + return am.firstAddr() +} + +// MainAccount returns the primary account used for transactions. +func (am *AccountManager) Default() (*Account, error) { + // TODO: persist main account address on disk + addr, err := am.firstAddr() + return &Account{Address: addr}, err +} + +func (am *AccountManager) firstAddr() ([]byte, error) { + addrs, err := am.keyStore.GetKeyAddresses() + if err != nil { + return nil, err + } + if len(addrs) == 0 { + return nil, ErrNoKeys + } + return addrs[0], nil +} + +func (am *AccountManager) DeleteAccount(address []byte, auth string) error { return am.keyStore.DeleteKey(address, auth) } diff --git a/core/manager.go b/core/manager.go index bb039d063..29f786653 100644 --- a/core/manager.go +++ b/core/manager.go @@ -1,7 +1,6 @@ package core import ( - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" @@ -14,7 +13,6 @@ type Backend interface { PeerCount() int IsListening() bool Peers() []*p2p.Peer - KeyManager() *crypto.KeyManager Db() ethutil.Database EventMux() *event.TypeMux } diff --git a/eth/backend.go b/eth/backend.go index 02e7e2746..cc5284dfa 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -38,11 +38,9 @@ var ( type Config struct { Name string - KeyStore string DataDir string LogFile string LogLevel int - KeyRing string LogFormat string MaxPeers int @@ -60,9 +58,8 @@ type Config struct { Shh bool Dial bool - MinerThreads int - - KeyManager *crypto.KeyManager + MinerThreads int + AccountManager *accounts.AccountManager } func (cfg *Config) parseBootNodes() []*discover.Node { @@ -127,8 +124,7 @@ type Ethereum struct { blockSub event.Subscription miner *miner.Miner - RpcServer rpc.RpcServer - keyManager *crypto.KeyManager + RpcServer rpc.RpcServer logger logger.LogSystem @@ -153,35 +149,22 @@ func New(config *Config) (*Ethereum, error) { return nil, fmt.Errorf("Database version mismatch. Protocol(%d / %d). `rm -rf %s`", protov, ProtocolVersion, path) } - // Create new keymanager - var keyManager *crypto.KeyManager - switch config.KeyStore { - case "db": - keyManager = crypto.NewDBKeyManager(db) - case "file": - keyManager = crypto.NewFileKeyManager(config.DataDir) - default: - return nil, fmt.Errorf("unknown keystore type: %s", config.KeyStore) - } - // Initialise the keyring - keyManager.Init(config.KeyRing, 0, false) - saveProtocolVersion(db) //ethutil.Config.Db = db eth := &Ethereum{ - shutdownChan: make(chan bool), - db: db, - keyManager: keyManager, - eventMux: &event.TypeMux{}, - logger: ethlogger, - DataDir: config.DataDir, + shutdownChan: make(chan bool), + db: db, + eventMux: &event.TypeMux{}, + logger: ethlogger, + accountManager: config.AccountManager, + DataDir: config.DataDir, } - // TODO: add config flag and case on plain/protected key store - ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir()) - am := accounts.NewAccountManager(ks, 300000) // keys unlocked for 300s - eth.accountManager = &am + cb, err := eth.accountManager.Coinbase() + if err != nil { + return nil, fmt.Errorf("no coinbase: %v", err) + } eth.chainManager = core.NewChainManager(db, eth.EventMux()) pow := ethash.New(eth.chainManager) @@ -189,7 +172,7 @@ func New(config *Config) (*Ethereum, error) { eth.blockProcessor = core.NewBlockProcessor(db, pow, eth.txPool, eth.chainManager, eth.EventMux()) eth.chainManager.SetProcessor(eth.blockProcessor) eth.whisper = whisper.New() - eth.miner = miner.New(keyManager.Address(), eth, pow, config.MinerThreads) + eth.miner = miner.New(cb, eth, pow, config.MinerThreads) hasBlock := eth.chainManager.HasBlock insertChain := eth.chainManager.InsertChain @@ -221,7 +204,6 @@ func New(config *Config) (*Ethereum, error) { return eth, nil } -func (s *Ethereum) KeyManager() *crypto.KeyManager { return s.keyManager } func (s *Ethereum) Logger() logger.LogSystem { return s.logger } func (s *Ethereum) Name() string { return s.net.Name } func (s *Ethereum) AccountManager() *accounts.AccountManager { return s.accountManager } @@ -237,7 +219,6 @@ func (s *Ethereum) IsListening() bool { return true } // func (s *Ethereum) PeerCount() int { return s.net.PeerCount() } func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } -func (s *Ethereum) Coinbase() []byte { return nil } // TODO // Start the ethereum func (s *Ethereum) Start() error { diff --git a/xeth/xeth.go b/xeth/xeth.go index 91bd35f8e..1ad62a7bf 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -32,7 +32,6 @@ type Backend interface { PeerCount() int IsListening() bool Peers() []*p2p.Peer - KeyManager() *crypto.KeyManager Db() ethutil.Database EventMux() *event.TypeMux Whisper() *whisper.Whisper @@ -142,7 +141,8 @@ func (self *XEth) IsListening() bool { } func (self *XEth) Coinbase() string { - return toHex(self.eth.KeyManager().Address()) + cb, _ := self.eth.AccountManager().Coinbase() + return toHex(cb) } func (self *XEth) NumberToHuman(balance string) string { @@ -251,10 +251,13 @@ func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (st gasPriceStr = "1" } + acct, err := self.accountManager.Default() + if err != nil { + return "", err + } var ( statedb = self.State().State() //self.chainManager.TransState() - key = self.eth.KeyManager().KeyPair() - from = statedb.GetOrNewStateObject(key.Address()) + from = statedb.GetOrNewStateObject(acct.Address) block = self.chainManager.CurrentBlock() to = statedb.GetOrNewStateObject(fromHex(toStr)) data = fromHex(dataStr) @@ -264,9 +267,12 @@ func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (st ) msg := types.NewTransactionMessage(fromHex(toStr), value, gas, price, data) - msg.Sign(key.PrivateKey) + sig, err := self.accountManager.Sign(acct, msg.Hash()) + if err != nil { + return "", err + } + msg.SetSignatureValues(sig) vmenv := core.NewEnv(statedb, self.chainManager, msg, block) - res, err := vmenv.Call(from, to.Address(), data, gas, price, value) if err != nil { return "", err From a2810c06d7cfc64e1636fe4ecfd5e35cc52b0d2b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sat, 7 Mar 2015 12:39:52 +0100 Subject: [PATCH 03/37] cmd/ethereum: add account commands --- cmd/ethereum/main.go | 79 +++++++++++++++++++++++++++++++++++++++----- cmd/utils/flags.go | 48 ++++++++++++--------------- 2 files changed, 93 insertions(+), 34 deletions(-) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 1133bd6f7..f12616e17 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -21,6 +21,7 @@ package main import ( + "bufio" "fmt" "os" "runtime" @@ -34,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/state" + "github.com/peterh/liner" ) const ( @@ -60,6 +62,23 @@ func init() { The output of this command is supposed to be machine-readable. `, }, + { + Action: accountList, + Name: "account", + Usage: "manage accounts", + Subcommands: []cli.Command{ + { + Action: accountList, + Name: "list", + Usage: "print account addresses", + }, + { + Action: accountCreate, + Name: "new", + Usage: "create a new account", + }, + }, + }, { Action: dump, Name: "dump", @@ -93,8 +112,6 @@ runtime will execute the file and exit. app.Flags = []cli.Flag{ utils.BootnodesFlag, utils.DataDirFlag, - utils.KeyRingFlag, - utils.KeyStoreFlag, utils.ListenPortFlag, utils.LogFileFlag, utils.LogFormatFlag, @@ -166,6 +183,37 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) { } } +func accountList(ctx *cli.Context) { + am := utils.GetAccountManager(ctx) + accts, err := am.Accounts() + if err != nil { + utils.Fatalf("Could not list accounts: %v", err) + } + for _, acct := range accts { + fmt.Printf("Address: %#x\n", acct) + } +} + +func accountCreate(ctx *cli.Context) { + am := utils.GetAccountManager(ctx) + auth, err := readPassword("Passphrase: ", true) + if err != nil { + utils.Fatalf("%v", err) + } + confirm, err := readPassword("Repeat Passphrase: ", false) + if err != nil { + utils.Fatalf("%v", err) + } + if auth != confirm { + utils.Fatalf("Passphrases did not match.") + } + acct, err := am.NewAccount(auth) + if err != nil { + utils.Fatalf("Could not create the account: %v", err) + } + fmt.Printf("Address: %#x\n", acct.Address) +} + func importchain(ctx *cli.Context) { if len(ctx.Args()) != 1 { utils.Fatalf("This command requires an argument.") @@ -201,12 +249,6 @@ func dump(ctx *cli.Context) { } } -// hashish returns true for strings that look like hashes. -func hashish(x string) bool { - _, err := strconv.Atoi(x) - return err != nil -} - func version(c *cli.Context) { fmt.Printf(`%v %v PV=%d @@ -216,3 +258,24 @@ GOPATH=%s GOROOT=%s `, ClientIdentifier, Version, eth.ProtocolVersion, runtime.GOOS, runtime.Version(), os.Getenv("GOPATH"), runtime.GOROOT()) } + +// hashish returns true for strings that look like hashes. +func hashish(x string) bool { + _, err := strconv.Atoi(x) + return err != nil +} + +func readPassword(prompt string, warnTerm bool) (string, error) { + if liner.TerminalSupported() { + lr := liner.NewLiner() + defer lr.Close() + return lr.PasswordPrompt(prompt) + } + if warnTerm { + fmt.Println("!! Unsupported terminal, password will be echoed.") + } + fmt.Print(prompt) + input, err := bufio.NewReader(os.Stdin).ReadString('\n') + fmt.Println() + return input, err +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fb80ac708..4f3ecd2b2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -4,8 +4,10 @@ import ( "crypto/ecdsa" "path" "runtime" + "time" "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -30,16 +32,6 @@ var ( Name: "vm", Usage: "Virtual Machine type: 0 is standard VM, 1 is debug VM", } - KeyRingFlag = cli.StringFlag{ - Name: "keyring", - Usage: "Name of keyring to be used", - Value: "", - } - KeyStoreFlag = cli.StringFlag{ - Name: "keystore", - Usage: `Where to store keyrings: "db" or "file"`, - Value: "db", - } DataDirFlag = cli.StringFlag{ Name: "datadir", Usage: "Data directory to be used", @@ -145,22 +137,20 @@ func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) { func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum { ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(clientID, version), - KeyStore: ctx.GlobalString(KeyStoreFlag.Name), - DataDir: ctx.GlobalString(DataDirFlag.Name), - LogFile: ctx.GlobalString(LogFileFlag.Name), - LogLevel: ctx.GlobalInt(LogLevelFlag.Name), - LogFormat: ctx.GlobalString(LogFormatFlag.Name), - MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), - - MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), - Port: ctx.GlobalString(ListenPortFlag.Name), - NAT: GetNAT(ctx), - NodeKey: GetNodeKey(ctx), - KeyRing: ctx.GlobalString(KeyRingFlag.Name), - Shh: true, - Dial: true, - BootNodes: ctx.GlobalString(BootnodesFlag.Name), + Name: p2p.MakeName(clientID, version), + DataDir: ctx.GlobalString(DataDirFlag.Name), + LogFile: ctx.GlobalString(LogFileFlag.Name), + LogLevel: ctx.GlobalInt(LogLevelFlag.Name), + LogFormat: ctx.GlobalString(LogFormatFlag.Name), + MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), + AccountManager: GetAccountManager(ctx), + MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), + Port: ctx.GlobalString(ListenPortFlag.Name), + NAT: GetNAT(ctx), + NodeKey: GetNodeKey(ctx), + Shh: true, + Dial: true, + BootNodes: ctx.GlobalString(BootnodesFlag.Name), }) if err != nil { exit(err) @@ -176,3 +166,9 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database) { } return core.NewChainManager(db, new(event.TypeMux)), db } + +func GetAccountManager(ctx *cli.Context) *accounts.AccountManager { + dataDir := ctx.GlobalString(DataDirFlag.Name) + ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) + return accounts.NewAccountManager(ks, 300*time.Second) +} From fda7b4c79d070f1cb4f5d7ef5b4d077d9dcf2774 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 8 Mar 2015 00:18:13 +0100 Subject: [PATCH 04/37] accounts: use pointers consistently Account is now always a non-pointer. This will be important once the manager starts remembering accounts. AccountManager is now always a pointer because it contains locks and locks cannot be copied. --- accounts/account_manager.go | 26 +++++++++++--------------- accounts/accounts_test.go | 3 ++- xeth/xeth.go | 2 +- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 3b7785231..86f9c5916 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -47,7 +47,6 @@ var ( ErrNoKeys = errors.New("no keys in store") ) -// TODO: better name for this struct? type Account struct { Address []byte } @@ -74,10 +73,10 @@ func (am *AccountManager) Coinbase() (addr []byte, err error) { } // MainAccount returns the primary account used for transactions. -func (am *AccountManager) Default() (*Account, error) { +func (am *AccountManager) Default() (Account, error) { // TODO: persist main account address on disk addr, err := am.firstAddr() - return &Account{Address: addr}, err + return Account{Address: addr}, err } func (am *AccountManager) firstAddr() ([]byte, error) { @@ -95,9 +94,9 @@ func (am *AccountManager) DeleteAccount(address []byte, auth string) error { return am.keyStore.DeleteKey(address, auth) } -func (am *AccountManager) Sign(fromAccount *Account, toSign []byte) (signature []byte, err error) { +func (am *AccountManager) Sign(a Account, toSign []byte) (signature []byte, err error) { am.mutex.RLock() - unlockedKey := am.unlockedKeys[string(fromAccount.Address)] + unlockedKey := am.unlockedKeys[string(a.Address)] am.mutex.RUnlock() if unlockedKey.Address == nil { return nil, ErrLocked @@ -106,28 +105,25 @@ func (am *AccountManager) Sign(fromAccount *Account, toSign []byte) (signature [ return signature, err } -func (am *AccountManager) SignLocked(fromAccount *Account, keyAuth string, toSign []byte) (signature []byte, err error) { - key, err := am.keyStore.GetKey(fromAccount.Address, keyAuth) +func (am *AccountManager) SignLocked(a Account, keyAuth string, toSign []byte) (signature []byte, err error) { + key, err := am.keyStore.GetKey(a.Address, keyAuth) if err != nil { return nil, err } am.mutex.RLock() - am.unlockedKeys[string(fromAccount.Address)] = *key + am.unlockedKeys[string(a.Address)] = *key am.mutex.RUnlock() - go unlockLater(am, fromAccount.Address) + go unlockLater(am, a.Address) signature, err = crypto.Sign(toSign, key.PrivateKey) return signature, err } -func (am AccountManager) NewAccount(auth string) (*Account, error) { +func (am *AccountManager) NewAccount(auth string) (Account, error) { key, err := am.keyStore.GenerateNewKey(crand.Reader, auth) if err != nil { - return nil, err + return Account{}, err } - ua := &Account{ - Address: key.Address, - } - return ua, err + return Account{Address: key.Address}, nil } func (am *AccountManager) Accounts() ([]Account, error) { diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 44d1d72f1..d8187220a 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -3,10 +3,11 @@ package accounts import ( "testing" + "time" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/ethereum/go-ethereum/ethutil" - "time" ) func TestAccountManager(t *testing.T) { diff --git a/xeth/xeth.go b/xeth/xeth.go index 1ad62a7bf..187aa8c0f 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -311,7 +311,7 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt nonce := state.GetNonce(from) tx.SetNonce(nonce) - sig, err := self.accountManager.Sign(&accounts.Account{Address: from}, tx.Hash()) + sig, err := self.accountManager.Sign(accounts.Account{Address: from}, tx.Hash()) if err != nil { return "", err } From afc530ea411e18223b0323d7e11aa0fab9289d65 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 8 Mar 2015 00:35:23 +0100 Subject: [PATCH 05/37] accounts: use time.Duration correctly There is no point to using time.Duration if the value is interpreted as milliseconds. Callers should use the standard multiplication idiom to choose the unit. In fact, the only caller outside of the tests already does so. --- accounts/account_manager.go | 19 ++++++++++--------- accounts/accounts_test.go | 8 ++++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 86f9c5916..f87cce65f 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -33,6 +33,7 @@ and accounts persistence is derived from stored keys' addresses package accounts import ( + "crypto/ecdsa" crand "crypto/rand" "errors" @@ -52,17 +53,17 @@ type Account struct { } type AccountManager struct { - keyStore crypto.KeyStore2 - unlockedKeys map[string]crypto.Key - unlockMilliseconds time.Duration - mutex sync.RWMutex + keyStore crypto.KeyStore2 + unlockedKeys map[string]crypto.Key + unlockTime time.Duration + mutex sync.RWMutex } -func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) *AccountManager { +func NewAccountManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *AccountManager { return &AccountManager{ - keyStore: keyStore, - unlockedKeys: make(map[string]crypto.Key), - unlockMilliseconds: unlockMilliseconds, + keyStore: keyStore, + unlockedKeys: make(map[string]crypto.Key), + unlockTime: unlockTime, } } @@ -144,7 +145,7 @@ func (am *AccountManager) Accounts() ([]Account, error) { func unlockLater(am *AccountManager, addr []byte) { select { - case <-time.After(time.Millisecond * am.unlockMilliseconds): + case <-time.After(am.unlockTime): } am.mutex.RLock() // TODO: how do we know the key is actually gone from memory? diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index d8187220a..30e0b011a 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -12,7 +12,7 @@ import ( func TestAccountManager(t *testing.T) { ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 100) + am := NewAccountManager(ks, 100*time.Millisecond) pass := "" // not used but required by API a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -22,7 +22,7 @@ func TestAccountManager(t *testing.T) { } // Cleanup - time.Sleep(time.Millisecond * 150) // wait for locking + time.Sleep(150 * time.Millisecond) // wait for locking accounts, err := am.Accounts() if err != nil { @@ -38,7 +38,7 @@ func TestAccountManager(t *testing.T) { func TestAccountManagerLocking(t *testing.T) { ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 200) + am := NewAccountManager(ks, 200*time.Millisecond) pass := "foo" a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -62,7 +62,7 @@ func TestAccountManagerLocking(t *testing.T) { } // Signing without passphrase fails after automatic locking - time.Sleep(time.Millisecond * time.Duration(250)) + time.Sleep(250 * time.Millisecond) _, err = am.Sign(a1, toSign) if err != ErrLocked { From d6a7332993cf32960ef94947341cedd3061559a7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 8 Mar 2015 00:36:06 +0100 Subject: [PATCH 06/37] accounts: fix uses of sync.RWMutex RWMutexes must be write-locked when writing in order to actually protect the writes. --- accounts/account_manager.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index f87cce65f..c0f2953bd 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -111,9 +111,9 @@ func (am *AccountManager) SignLocked(a Account, keyAuth string, toSign []byte) ( if err != nil { return nil, err } - am.mutex.RLock() + am.mutex.Lock() am.unlockedKeys[string(a.Address)] = *key - am.mutex.RUnlock() + am.mutex.Unlock() go unlockLater(am, a.Address) signature, err = crypto.Sign(toSign, key.PrivateKey) return signature, err @@ -147,8 +147,10 @@ func unlockLater(am *AccountManager, addr []byte) { select { case <-time.After(am.unlockTime): } - am.mutex.RLock() + am.mutex.Lock() // TODO: how do we know the key is actually gone from memory? delete(am.unlockedKeys, string(addr)) - am.mutex.RUnlock() + am.mutex.Unlock() +} + } From 3750ec7b7de04d8482b798e73f04637ea9e1ca89 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 8 Mar 2015 01:27:30 +0100 Subject: [PATCH 07/37] accounts: prevent early drops and zero keys in memory when dropping Private keys would be locked early if SignLocked was called more than once because the unlockLater was still running. Terminate it properly. --- accounts/account_manager.go | 69 +++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index c0f2953bd..bb6d970b2 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -53,17 +53,24 @@ type Account struct { } type AccountManager struct { - keyStore crypto.KeyStore2 - unlockedKeys map[string]crypto.Key - unlockTime time.Duration - mutex sync.RWMutex + keyStore crypto.KeyStore2 + unlocked map[string]*unlocked + unlockTime time.Duration + mutex sync.RWMutex +} + +type unlocked struct { + addr []byte + abort chan struct{} + + *crypto.Key } func NewAccountManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *AccountManager { return &AccountManager{ - keyStore: keyStore, - unlockedKeys: make(map[string]crypto.Key), - unlockTime: unlockTime, + keyStore: keyStore, + unlocked: make(map[string]*unlocked), + unlockTime: unlockTime, } } @@ -97,9 +104,9 @@ func (am *AccountManager) DeleteAccount(address []byte, auth string) error { func (am *AccountManager) Sign(a Account, toSign []byte) (signature []byte, err error) { am.mutex.RLock() - unlockedKey := am.unlockedKeys[string(a.Address)] + unlockedKey, found := am.unlocked[string(a.Address)] am.mutex.RUnlock() - if unlockedKey.Address == nil { + if !found { return nil, ErrLocked } signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey) @@ -111,10 +118,8 @@ func (am *AccountManager) SignLocked(a Account, keyAuth string, toSign []byte) ( if err != nil { return nil, err } - am.mutex.Lock() - am.unlockedKeys[string(a.Address)] = *key - am.mutex.Unlock() - go unlockLater(am, a.Address) + u := am.addUnlocked(a.Address, key) + go am.dropLater(u) signature, err = crypto.Sign(toSign, key.PrivateKey) return signature, err } @@ -143,14 +148,40 @@ func (am *AccountManager) Accounts() ([]Account, error) { return accounts, err } -func unlockLater(am *AccountManager, addr []byte) { - select { - case <-time.After(am.unlockTime): - } +func (am *AccountManager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { + u := &unlocked{addr: addr, abort: make(chan struct{}), Key: key} am.mutex.Lock() - // TODO: how do we know the key is actually gone from memory? - delete(am.unlockedKeys, string(addr)) + prev, found := am.unlocked[string(addr)] + if found { + // terminate dropLater for this key to avoid unexpected drops. + close(prev.abort) + zeroKey(prev.PrivateKey) + } + am.unlocked[string(addr)] = u am.mutex.Unlock() + return u } +func (am *AccountManager) dropLater(u *unlocked) { + t := time.NewTimer(am.unlockTime) + defer t.Stop() + select { + case <-u.abort: + // just quit + case <-t.C: + am.mutex.Lock() + if am.unlocked[string(u.addr)] == u { + zeroKey(u.PrivateKey) + delete(am.unlocked, string(u.addr)) + } + am.mutex.Unlock() + } +} + +// zeroKey zeroes a private key in memory. +func zeroKey(k *ecdsa.PrivateKey) { + b := k.D.Bits() + for i := range b { + b[i] = 0 + } } From fb53a9362e1238d8edb466d77427dc3cbb13eb20 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 8 Mar 2015 01:52:49 +0100 Subject: [PATCH 08/37] accounts: AccountManager -> Manager --- accounts/account_manager.go | 26 ++++++++++++------------ accounts/accounts_test.go | 4 ++-- cmd/utils/flags.go | 4 ++-- eth/backend.go | 34 ++++++++++++++++---------------- javascript/javascript_runtime.go | 1 + xeth/xeth.go | 4 ++-- 6 files changed, 37 insertions(+), 36 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index bb6d970b2..97cf7c878 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -52,7 +52,7 @@ type Account struct { Address []byte } -type AccountManager struct { +type Manager struct { keyStore crypto.KeyStore2 unlocked map[string]*unlocked unlockTime time.Duration @@ -66,8 +66,8 @@ type unlocked struct { *crypto.Key } -func NewAccountManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *AccountManager { - return &AccountManager{ +func NewManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *Manager { + return &Manager{ keyStore: keyStore, unlocked: make(map[string]*unlocked), unlockTime: unlockTime, @@ -75,19 +75,19 @@ func NewAccountManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *Acc } // Coinbase returns the account address that mining rewards are sent to. -func (am *AccountManager) Coinbase() (addr []byte, err error) { +func (am *Manager) Coinbase() (addr []byte, err error) { // TODO: persist coinbase address on disk return am.firstAddr() } // MainAccount returns the primary account used for transactions. -func (am *AccountManager) Default() (Account, error) { +func (am *Manager) Default() (Account, error) { // TODO: persist main account address on disk addr, err := am.firstAddr() return Account{Address: addr}, err } -func (am *AccountManager) firstAddr() ([]byte, error) { +func (am *Manager) firstAddr() ([]byte, error) { addrs, err := am.keyStore.GetKeyAddresses() if err != nil { return nil, err @@ -98,11 +98,11 @@ func (am *AccountManager) firstAddr() ([]byte, error) { return addrs[0], nil } -func (am *AccountManager) DeleteAccount(address []byte, auth string) error { +func (am *Manager) DeleteAccount(address []byte, auth string) error { return am.keyStore.DeleteKey(address, auth) } -func (am *AccountManager) Sign(a Account, toSign []byte) (signature []byte, err error) { +func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) { am.mutex.RLock() unlockedKey, found := am.unlocked[string(a.Address)] am.mutex.RUnlock() @@ -113,7 +113,7 @@ func (am *AccountManager) Sign(a Account, toSign []byte) (signature []byte, err return signature, err } -func (am *AccountManager) SignLocked(a Account, keyAuth string, toSign []byte) (signature []byte, err error) { +func (am *Manager) SignLocked(a Account, keyAuth string, toSign []byte) (signature []byte, err error) { key, err := am.keyStore.GetKey(a.Address, keyAuth) if err != nil { return nil, err @@ -124,7 +124,7 @@ func (am *AccountManager) SignLocked(a Account, keyAuth string, toSign []byte) ( return signature, err } -func (am *AccountManager) NewAccount(auth string) (Account, error) { +func (am *Manager) NewAccount(auth string) (Account, error) { key, err := am.keyStore.GenerateNewKey(crand.Reader, auth) if err != nil { return Account{}, err @@ -132,7 +132,7 @@ func (am *AccountManager) NewAccount(auth string) (Account, error) { return Account{Address: key.Address}, nil } -func (am *AccountManager) Accounts() ([]Account, error) { +func (am *Manager) Accounts() ([]Account, error) { addresses, err := am.keyStore.GetKeyAddresses() if err != nil { return nil, err @@ -148,7 +148,7 @@ func (am *AccountManager) Accounts() ([]Account, error) { return accounts, err } -func (am *AccountManager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { +func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { u := &unlocked{addr: addr, abort: make(chan struct{}), Key: key} am.mutex.Lock() prev, found := am.unlocked[string(addr)] @@ -162,7 +162,7 @@ func (am *AccountManager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { return u } -func (am *AccountManager) dropLater(u *unlocked) { +func (am *Manager) dropLater(u *unlocked) { t := time.NewTimer(am.unlockTime) defer t.Stop() select { diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 30e0b011a..b90da2892 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -12,7 +12,7 @@ import ( func TestAccountManager(t *testing.T) { ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 100*time.Millisecond) + am := NewManager(ks, 100*time.Millisecond) pass := "" // not used but required by API a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -38,7 +38,7 @@ func TestAccountManager(t *testing.T) { func TestAccountManagerLocking(t *testing.T) { ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 200*time.Millisecond) + am := NewManager(ks, 200*time.Millisecond) pass := "foo" a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 4f3ecd2b2..e6eee20a7 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -167,8 +167,8 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database) { return core.NewChainManager(db, new(event.TypeMux)), db } -func GetAccountManager(ctx *cli.Context) *accounts.AccountManager { +func GetAccountManager(ctx *cli.Context) *accounts.Manager { dataDir := ctx.GlobalString(DataDirFlag.Name) ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) - return accounts.NewAccountManager(ks, 300*time.Second) + return accounts.NewManager(ks, 300*time.Second) } diff --git a/eth/backend.go b/eth/backend.go index cc5284dfa..680cc175a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -59,7 +59,7 @@ type Config struct { Dial bool MinerThreads int - AccountManager *accounts.AccountManager + AccountManager *accounts.Manager } func (cfg *Config) parseBootNodes() []*discover.Node { @@ -115,7 +115,7 @@ type Ethereum struct { txPool *core.TxPool chainManager *core.ChainManager blockPool *blockpool.BlockPool - accountManager *accounts.AccountManager + accountManager *accounts.Manager whisper *whisper.Whisper net *p2p.Server @@ -204,21 +204,21 @@ func New(config *Config) (*Ethereum, error) { return eth, nil } -func (s *Ethereum) Logger() logger.LogSystem { return s.logger } -func (s *Ethereum) Name() string { return s.net.Name } -func (s *Ethereum) AccountManager() *accounts.AccountManager { return s.accountManager } -func (s *Ethereum) ChainManager() *core.ChainManager { return s.chainManager } -func (s *Ethereum) BlockProcessor() *core.BlockProcessor { return s.blockProcessor } -func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } -func (s *Ethereum) BlockPool() *blockpool.BlockPool { return s.blockPool } -func (s *Ethereum) Whisper() *whisper.Whisper { return s.whisper } -func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *Ethereum) Db() ethutil.Database { return s.db } -func (s *Ethereum) Miner() *miner.Miner { return s.miner } -func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) PeerCount() int { return s.net.PeerCount() } -func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } -func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } +func (s *Ethereum) Logger() logger.LogSystem { return s.logger } +func (s *Ethereum) Name() string { return s.net.Name } +func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } +func (s *Ethereum) ChainManager() *core.ChainManager { return s.chainManager } +func (s *Ethereum) BlockProcessor() *core.BlockProcessor { return s.blockProcessor } +func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } +func (s *Ethereum) BlockPool() *blockpool.BlockPool { return s.blockPool } +func (s *Ethereum) Whisper() *whisper.Whisper { return s.whisper } +func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } +func (s *Ethereum) Db() ethutil.Database { return s.db } +func (s *Ethereum) Miner() *miner.Miner { return s.miner } +func (s *Ethereum) IsListening() bool { return true } // Always listening +func (s *Ethereum) PeerCount() int { return s.net.PeerCount() } +func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } +func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } // Start the ethereum func (s *Ethereum) Start() error { diff --git a/javascript/javascript_runtime.go b/javascript/javascript_runtime.go index 36b14a057..0a137f72a 100644 --- a/javascript/javascript_runtime.go +++ b/javascript/javascript_runtime.go @@ -6,6 +6,7 @@ import ( "os" "path" "path/filepath" + "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/xeth" "github.com/obscuren/otto" diff --git a/xeth/xeth.go b/xeth/xeth.go index 187aa8c0f..afe680f34 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -27,7 +27,7 @@ var pipelogger = logger.NewLogger("XETH") type Backend interface { BlockProcessor() *core.BlockProcessor ChainManager() *core.ChainManager - AccountManager() *accounts.AccountManager + AccountManager() *accounts.Manager TxPool() *core.TxPool PeerCount() int IsListening() bool @@ -42,7 +42,7 @@ type XEth struct { eth Backend blockProcessor *core.BlockProcessor chainManager *core.ChainManager - accountManager *accounts.AccountManager + accountManager *accounts.Manager state *State whisper *Whisper miner *miner.Miner From 6684ef201a1a133aafdec6b24564533756de3cd4 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 8 Mar 2015 02:45:02 +0100 Subject: [PATCH 09/37] accounts: don't store address in unlocked and add commentary This was suggested during review. --- accounts/account_manager.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 97cf7c878..bb664878a 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -60,10 +60,8 @@ type Manager struct { } type unlocked struct { - addr []byte - abort chan struct{} - *crypto.Key + abort chan struct{} } func NewManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *Manager { @@ -119,7 +117,7 @@ func (am *Manager) SignLocked(a Account, keyAuth string, toSign []byte) (signatu return nil, err } u := am.addUnlocked(a.Address, key) - go am.dropLater(u) + go am.dropLater(a.Address, u) signature, err = crypto.Sign(toSign, key.PrivateKey) return signature, err } @@ -149,7 +147,7 @@ func (am *Manager) Accounts() ([]Account, error) { } func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { - u := &unlocked{addr: addr, abort: make(chan struct{}), Key: key} + u := &unlocked{Key: key, abort: make(chan struct{})} am.mutex.Lock() prev, found := am.unlocked[string(addr)] if found { @@ -162,7 +160,7 @@ func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { return u } -func (am *Manager) dropLater(u *unlocked) { +func (am *Manager) dropLater(addr []byte, u *unlocked) { t := time.NewTimer(am.unlockTime) defer t.Stop() select { @@ -170,9 +168,13 @@ func (am *Manager) dropLater(u *unlocked) { // just quit case <-t.C: am.mutex.Lock() - if am.unlocked[string(u.addr)] == u { + // only drop if it's still the same key instance that dropLater + // was launched with. we can check that using pointer equality + // because the map stores a new pointer every time the key is + // unlocked. + if am.unlocked[string(addr)] == u { zeroKey(u.PrivateKey) - delete(am.unlocked, string(u.addr)) + delete(am.unlocked, string(addr)) } am.mutex.Unlock() } From 2dacb51fb02816c7591bfdbaaf7a160e122e5590 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 16:58:06 +0100 Subject: [PATCH 10/37] cmd/blocktest: simplify to fix build block tests only need the chain, not all of ethereum. --- cmd/blocktest/flags.go | 41 --------- cmd/blocktest/main.go | 184 ++++++++--------------------------------- 2 files changed, 35 insertions(+), 190 deletions(-) delete mode 100644 cmd/blocktest/flags.go diff --git a/cmd/blocktest/flags.go b/cmd/blocktest/flags.go deleted file mode 100644 index c811e5b85..000000000 --- a/cmd/blocktest/flags.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum 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. - - go-ethereum 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 go-ethereum. If not, see . -*/ -/** - * @authors - * Gustav Simonsson - */ -package main - -import ( - "flag" - "fmt" - "os" -) - -var ( - TestFile string -) - -func Init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s \n", os.Args[0]) - flag.PrintDefaults() - } - flag.Parse() - - TestFile = flag.Arg(0) -} diff --git a/cmd/blocktest/main.go b/cmd/blocktest/main.go index 4a05b8bee..b96f42710 100644 --- a/cmd/blocktest/main.go +++ b/cmd/blocktest/main.go @@ -25,34 +25,26 @@ package main import ( "bytes" - "crypto/ecdsa" "encoding/hex" "encoding/json" + "flag" "fmt" "io/ioutil" "log" "math/big" - "path" + "os" "runtime" - "strconv" "strings" - "time" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/core" types "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/rlp" ) -const ( - ClientIdentifier = "Ethereum(G)" - Version = "0.8.6" -) - type Account struct { Balance string Code string @@ -78,6 +70,7 @@ type BlockHeader struct { TransactionsTrie string UncleHash string } + type Tx struct { Data string GasLimit string @@ -103,103 +96,40 @@ type Test struct { Pre map[string]Account } -var ( - Identifier string - KeyRing string - DiffTool bool - DiffType string - KeyStore string - StartRpc bool - StartWebSockets bool - RpcListenAddress string - RpcPort int - WsPort int - OutboundPort string - ShowGenesis bool - AddPeer string - MaxPeer int - GenAddr bool - BootNodes string - NodeKey *ecdsa.PrivateKey - NAT nat.Interface - SecretFile string - ExportDir string - NonInteractive bool - Datadir string - LogFile string - ConfigFile string - DebugFile string - LogLevel int - LogFormat string - Dump bool - DumpHash string - DumpNumber int - VmType int - ImportChain string - SHH bool - Dial bool - PrintVersion bool - MinerThreads int -) - -// flags specific to cli client -var ( - StartMining bool - StartJsConsole bool - InputFile string -) - func main() { - init_vars() - - Init() - - if len(TestFile) < 1 { - log.Fatal("Please specify test file") - } - blocks, err := loadBlocksFromTestFile(TestFile) - if err != nil { - panic(err) + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "%s \n", os.Args[0]) + flag.PrintDefaults() } + flag.Parse() runtime.GOMAXPROCS(runtime.NumCPU()) + logger.AddLogSystem(logger.NewStdLogSystem(os.Stderr, log.LstdFlags, logger.DebugDetailLevel)) + defer func() { logger.Flush() }() - defer func() { - logger.Flush() - }() + if len(os.Args) < 2 { + utils.Fatalf("Please specify a test file as the first argument.") + } + blocks, err := loadBlocksFromTestFile(os.Args[1]) + if err != nil { + utils.Fatalf("Could not load blocks: %v", err) + } - utils.HandleInterrupt() + chain := memchain() + chain.ResetWithGenesisBlock(blocks[0]) + if err = chain.InsertChain(types.Blocks{blocks[1]}); err != nil { + utils.Fatalf("Error: %v", err) + } else { + fmt.Println("PASS") + } +} - utils.InitConfig(VmType, ConfigFile, Datadir, "ethblocktest") - - ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(ClientIdentifier, Version), - KeyStore: KeyStore, - DataDir: Datadir, - LogFile: LogFile, - LogLevel: LogLevel, - LogFormat: LogFormat, - MaxPeers: MaxPeer, - Port: OutboundPort, - NAT: NAT, - KeyRing: KeyRing, - Shh: true, - Dial: Dial, - BootNodes: BootNodes, - NodeKey: NodeKey, - MinerThreads: MinerThreads, - }) - - utils.StartRpc(ethereum, RpcListenAddress, RpcPort) - utils.StartEthereum(ethereum) - - ethereum.ChainManager().ResetWithGenesisBlock(blocks[0]) - - // fmt.Println("HURR: ", hex.EncodeToString(ethutil.Encode(blocks[0].RlpData()))) - - go ethereum.ChainManager().InsertChain(types.Blocks{blocks[1]}) - fmt.Println("OK! ") - ethereum.WaitForShutdown() +func memchain() *core.ChainManager { + db, err := ethdb.NewMemDatabase() + if err != nil { + utils.Fatalf("Could not create in-memory database: %v", err) + } + return core.NewChainManager(db, new(event.TypeMux)) } func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { @@ -207,9 +137,8 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { if err != nil { return } - bt := *new(map[string]Test) - err = json.Unmarshal(fileContent, &bt) - if err != nil { + bt := make(map[string]Test) + if err = json.Unmarshal(fileContent, &bt); err != nil { return } @@ -272,49 +201,6 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { return } -func init_vars() { - VmType = 0 - Identifier = "" - KeyRing = "" - KeyStore = "db" - RpcListenAddress = "127.0.0.1" - RpcPort = 8545 - WsPort = 40404 - StartRpc = true - StartWebSockets = false - NonInteractive = false - GenAddr = false - SecretFile = "" - ExportDir = "" - LogFile = "" - - timeStr := strconv.FormatInt(time.Now().UnixNano(), 10) - - Datadir = path.Join(ethutil.DefaultDataDir(), timeStr) - ConfigFile = path.Join(ethutil.DefaultDataDir(), timeStr, "conf.ini") - - DebugFile = "" - LogLevel = 5 - LogFormat = "std" - DiffTool = false - DiffType = "all" - ShowGenesis = false - ImportChain = "" - Dump = false - DumpHash = "" - DumpNumber = -1 - StartMining = false - StartJsConsole = false - PrintVersion = false - MinerThreads = runtime.NumCPU() - - Dial = false - OutboundPort = "30303" - BootNodes = "" - MaxPeer = 1 - -} - func hex_decode(s string) (res []byte, err error) { return hex.DecodeString(strings.TrimPrefix(s, "0x")) } From 2ee88a220ac22a814def1d568f80d7f29ede7095 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 18:05:56 +0100 Subject: [PATCH 11/37] core: remove Hash method from Message interface This will simplify the next commmit. Hash was only used for logging. --- core/state_transition.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 00e383f3f..c487f8514 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -3,6 +3,7 @@ package core import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/state" @@ -44,8 +45,6 @@ type StateTransition struct { } type Message interface { - Hash() []byte - From() []byte To() []byte @@ -152,7 +151,7 @@ func (self *StateTransition) preCheck() (err error) { } func (self *StateTransition) TransitionState() (ret []byte, err error) { - statelogger.Debugf("(~) %x\n", self.msg.Hash()) + // statelogger.Debugf("(~) %x\n", self.msg.Hash()) // XXX Transactions after this point are considered valid. if err = self.preCheck(); err != nil { From b4fa94c4b1459e71d4f11a3178cf56edf2b4aed3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 18:04:40 +0100 Subject: [PATCH 12/37] xeth: don't sign transactions for tx call This should make calls faster and removes interaction with account manager. --- rpc/api.go | 2 +- xeth/xeth.go | 57 ++++++++++++++++++++++++++++------------------------ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/rpc/api.go b/rpc/api.go index b622945eb..9c792dd61 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -269,7 +269,7 @@ func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error { } func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error { - result, err := p.xeth().Call( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data) + result, err := p.xeth().Call(args.From, args.To, args.Value, args.Gas, args.GasPrice, args.Data) if err != nil { return err } diff --git a/xeth/xeth.go b/xeth/xeth.go index afe680f34..956b8cd01 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -7,6 +7,7 @@ package xeth import ( "bytes" "encoding/json" + "math/big" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core" @@ -243,7 +244,7 @@ func (self *XEth) PushTx(encodedTx string) (string, error) { return toHex(tx.Hash()), nil } -func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { +func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { if len(gasStr) == 0 { gasStr = "100000" } @@ -251,34 +252,20 @@ func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (st gasPriceStr = "1" } - acct, err := self.accountManager.Default() - if err != nil { - return "", err + statedb := self.State().State() //self.chainManager.TransState() + msg := callmsg{ + from: statedb.GetOrNewStateObject(fromHex(fromStr)), + to: fromHex(toStr), + gas: ethutil.Big(gasStr), + gasPrice: ethutil.Big(gasPriceStr), + value: ethutil.Big(valueStr), + data: fromHex(dataStr), } - var ( - statedb = self.State().State() //self.chainManager.TransState() - from = statedb.GetOrNewStateObject(acct.Address) - block = self.chainManager.CurrentBlock() - to = statedb.GetOrNewStateObject(fromHex(toStr)) - data = fromHex(dataStr) - gas = ethutil.Big(gasStr) - price = ethutil.Big(gasPriceStr) - value = ethutil.Big(valueStr) - ) - - msg := types.NewTransactionMessage(fromHex(toStr), value, gas, price, data) - sig, err := self.accountManager.Sign(acct, msg.Hash()) - if err != nil { - return "", err - } - msg.SetSignatureValues(sig) + block := self.chainManager.CurrentBlock() vmenv := core.NewEnv(statedb, self.chainManager, msg, block) - res, err := vmenv.Call(from, to.Address(), data, gas, price, value) - if err != nil { - return "", err - } - return toHex(res), nil + res, err := vmenv.Call(msg.from, msg.to, msg.data, msg.gas, msg.gasPrice, msg.value) + return toHex(res), err } func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { @@ -334,3 +321,21 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt return toHex(tx.Hash()), nil } + +// callmsg is the message type used for call transations. +type callmsg struct { + from *state.StateObject + to []byte + gas, gasPrice *big.Int + value *big.Int + data []byte +} + +// accessor boilerplate to implement core.Message +func (m callmsg) From() []byte { return m.from.Address() } +func (m callmsg) Nonce() uint64 { return m.from.Nonce() } +func (m callmsg) To() []byte { return m.to } +func (m callmsg) GasPrice() *big.Int { return m.gasPrice } +func (m callmsg) Gas() *big.Int { return m.gas } +func (m callmsg) Value() *big.Int { return m.value } +func (m callmsg) Data() []byte { return m.data } From 73d1ebe244644c2d74a1c0c38a3b339e72140886 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 22:51:50 +0100 Subject: [PATCH 13/37] cmd/utils: add NewApp --- cmd/ethereum/main.go | 6 +----- cmd/utils/flags.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index f12616e17..f5215c587 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -45,12 +45,10 @@ const ( var ( clilogger = logger.NewLogger("CLI") - app = cli.NewApp() + app = utils.NewApp(Version, "the go-ethereum command line interface") ) func init() { - app.Version = Version - app.Usage = "the go-ethereum command-line client" app.Action = run app.HideVersion = true // we have a command to print the version app.Commands = []cli.Command{ @@ -107,8 +105,6 @@ runtime will execute the file and exit. Usage: `import a blockchain file`, }, } - app.Author = "" - app.Email = "" app.Flags = []cli.Flag{ utils.BootnodesFlag, utils.DataDirFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e6eee20a7..2156963c4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -19,6 +19,17 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" ) +// NewApp creates an app with sane defaults. +func NewApp(version, usage string) *cli.App { + app := cli.NewApp() + app.Name = path.Base(os.Args[0]) + app.Author = "" + app.Email = "" + app.Version = version + app.Usage = usage + return app +} + // These are all the command line flags we support. // If you add to this list, please remember to include the // flag in the appropriate command definition. From a11f1d6a7ec2eaa1a348776072c49019368a5ef3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:00:27 +0100 Subject: [PATCH 14/37] rpc: add dataDir parameter and JSON-RPC handler --- cmd/ethereum/main.go | 4 +--- cmd/utils/cmd.go | 12 ------------ cmd/utils/flags.go | 18 ++++++++++++++++++ rpc/api.go | 5 +++-- rpc/http.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 rpc/http.go diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index f5215c587..4855a3e4a 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -170,9 +170,7 @@ func runjs(ctx *cli.Context) { func startEth(ctx *cli.Context, eth *eth.Ethereum) { utils.StartEthereum(eth) if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { - addr := ctx.GlobalString(utils.RPCListenAddrFlag.Name) - port := ctx.GlobalInt(utils.RPCPortFlag.Name) - utils.StartRpc(eth, addr, port) + utils.StartRPC(eth, ctx) } if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { eth.Miner().Start() diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 3c3d3955d..79ae7888a 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -34,9 +34,7 @@ import ( "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/rlp" - rpchttp "github.com/ethereum/go-ethereum/rpc/http" "github.com/ethereum/go-ethereum/state" - "github.com/ethereum/go-ethereum/xeth" ) var clilogger = logger.NewLogger("CLI") @@ -165,16 +163,6 @@ func KeyTasks(keyManager *crypto.KeyManager, KeyRing string, GenAddr bool, Secre clilogger.Infof("Main address %x\n", keyManager.Address()) } -func StartRpc(ethereum *eth.Ethereum, RpcListenAddress string, RpcPort int) { - var err error - ethereum.RpcServer, err = rpchttp.NewRpcHttpServer(xeth.New(ethereum), RpcListenAddress, RpcPort) - if err != nil { - clilogger.Errorf("Could not start RPC interface (port %v): %v", RpcPort, err) - } else { - go ethereum.RpcServer.Start() - } -} - func FormatTransactionData(data string) []byte { d := ethutil.StringToByteFunc(data, func(s string) (ret []byte) { slice := regexp.MustCompile("\\n|\\s").Split(s, 1000000000) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2156963c4..2995ebad8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2,6 +2,10 @@ package utils import ( "crypto/ecdsa" + "fmt" + "net" + "net/http" + "os" "path" "runtime" "time" @@ -17,6 +21,8 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/xeth" ) // NewApp creates an app with sane defaults. @@ -183,3 +189,15 @@ func GetAccountManager(ctx *cli.Context) *accounts.Manager { ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) return accounts.NewManager(ks, 300*time.Second) } + +func StartRPC(eth *eth.Ethereum, ctx *cli.Context) { + addr := ctx.GlobalString(RPCListenAddrFlag.Name) + port := ctx.GlobalInt(RPCPortFlag.Name) + dataDir := ctx.GlobalString(DataDirFlag.Name) + + l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port)) + if err != nil { + Fatalf("Can't listen on %s:%d: %v", addr, port, err) + } + go http.Serve(l, rpc.JSONRPC(xeth.New(eth), dataDir)) +} diff --git a/rpc/api.go b/rpc/api.go index 9c792dd61..c3aa7186b 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -11,6 +11,7 @@ package rpc import ( "fmt" "math/big" + "path" "strings" "sync" "time" @@ -55,8 +56,8 @@ type EthereumApi struct { defaultBlockAge int64 } -func NewEthereumApi(eth *xeth.XEth) *EthereumApi { - db, _ := ethdb.NewLDBDatabase("dapps") +func NewEthereumApi(eth *xeth.XEth, dataDir string) *EthereumApi { + db, _ := ethdb.NewLDBDatabase(path.Join(dataDir, "dapps")) api := &EthereumApi{ eth: eth, mux: eth.Backend().EventMux(), diff --git a/rpc/http.go b/rpc/http.go new file mode 100644 index 000000000..44e2ad6ab --- /dev/null +++ b/rpc/http.go @@ -0,0 +1,42 @@ +package rpc + +import ( + "net/http" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/xeth" +) + +var rpchttplogger = logger.NewLogger("RPC-HTTP") + +// JSONRPC returns a handler that implements the Ethereum JSON-RPC API. +func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { + var json JsonWrapper + const jsonrpcver = "2.0" + api := NewEthereumApi(pipe, dataDir) + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + + rpchttplogger.DebugDetailln("Handling request") + + reqParsed, reqerr := json.ParseRequestBody(req) + if reqerr != nil { + jsonerr := &RpcErrorObject{-32700, "Error: Could not parse request"} + json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + return + } + + var response interface{} + reserr := api.GetRequestReply(&reqParsed, &response) + if reserr != nil { + rpchttplogger.Warnln(reserr) + jsonerr := &RpcErrorObject{-32603, reserr.Error()} + json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) + return + } + + rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) + json.Send(w, &RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response}) + }) +} From 6a7e02fc9f7c7a92181ecdf3ce70dbdad3cb000e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:01:26 +0100 Subject: [PATCH 15/37] rpc/http: delete package --- rpc/http/server.go | 116 --------------------------------------------- 1 file changed, 116 deletions(-) delete mode 100644 rpc/http/server.go diff --git a/rpc/http/server.go b/rpc/http/server.go deleted file mode 100644 index 452b7c9af..000000000 --- a/rpc/http/server.go +++ /dev/null @@ -1,116 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum 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. - - go-ethereum 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 go-ethereum. If not, see . -*/ -package rpchttp - -import ( - "fmt" - "net" - "net/http" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/xeth" -) - -var rpchttplogger = logger.NewLogger("RPC-HTTP") -var JSON rpc.JsonWrapper - -func NewRpcHttpServer(pipe *xeth.XEth, address string, port int) (*RpcHttpServer, error) { - sport := fmt.Sprintf("%s:%d", address, port) - l, err := net.Listen("tcp", sport) - if err != nil { - return nil, err - } - - return &RpcHttpServer{ - listener: l, - quit: make(chan bool), - pipe: pipe, - port: port, - addr: address, - }, nil -} - -type RpcHttpServer struct { - quit chan bool - listener net.Listener - pipe *xeth.XEth - port int - addr string -} - -func (s *RpcHttpServer) exitHandler() { -out: - for { - select { - case <-s.quit: - s.listener.Close() - break out - } - } - - rpchttplogger.Infoln("Shutdown RPC-HTTP server") -} - -func (s *RpcHttpServer) Stop() { - close(s.quit) -} - -func (s *RpcHttpServer) Start() { - rpchttplogger.Infof("Starting RPC-HTTP server on %s:%d", s.addr, s.port) - go s.exitHandler() - - api := rpc.NewEthereumApi(s.pipe) - h := s.apiHandler(api) - http.Handle("/", h) - - err := http.Serve(s.listener, nil) - // FIX Complains on shutdown due to listner already being closed - if err != nil { - rpchttplogger.Errorln("Error on RPC-HTTP interface:", err) - } -} - -func (s *RpcHttpServer) apiHandler(api *rpc.EthereumApi) http.Handler { - var jsonrpcver string = "2.0" - fn := func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - - rpchttplogger.DebugDetailln("Handling request") - - reqParsed, reqerr := JSON.ParseRequestBody(req) - if reqerr != nil { - jsonerr := &rpc.RpcErrorObject{-32700, "Error: Could not parse request"} - JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) - return - } - - var response interface{} - reserr := api.GetRequestReply(&reqParsed, &response) - if reserr != nil { - rpchttplogger.Warnln(reserr) - jsonerr := &rpc.RpcErrorObject{-32603, reserr.Error()} - JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) - return - } - - rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) - JSON.Send(w, &rpc.RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response}) - } - - return http.HandlerFunc(fn) -} From 63758db37977bf4a2bd13cd360432c520dd77dd8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:01:36 +0100 Subject: [PATCH 16/37] eth: delete unused RpcServer field --- eth/backend.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 680cc175a..28ed0bbe8 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -20,7 +20,6 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/whisper" ) @@ -124,8 +123,6 @@ type Ethereum struct { blockSub event.Subscription miner *miner.Miner - RpcServer rpc.RpcServer - logger logger.LogSystem Mining bool @@ -268,10 +265,6 @@ func (s *Ethereum) Stop() { s.txSub.Unsubscribe() // quits txBroadcastLoop s.blockSub.Unsubscribe() // quits blockBroadcastLoop - if s.RpcServer != nil { - s.RpcServer.Stop() - } - s.txPool.Stop() s.eventMux.Stop() s.blockPool.Stop() From c2e5dacf555d93cb96b8fbffa700a2a6fece8bf0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:02:43 +0100 Subject: [PATCH 17/37] accounts: add Manager.HasAccount, delete Manager.Default --- accounts/account_manager.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index bb664878a..4575334bf 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -33,6 +33,7 @@ and accounts persistence is derived from stored keys' addresses package accounts import ( + "bytes" "crypto/ecdsa" crand "crypto/rand" @@ -72,19 +73,22 @@ func NewManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *Manager { } } +func (am *Manager) HasAccount(addr []byte) bool { + accounts, _ := am.Accounts() + for _, acct := range accounts { + if bytes.Compare(acct.Address, addr) == 0 { + return true + } + } + return false +} + // Coinbase returns the account address that mining rewards are sent to. func (am *Manager) Coinbase() (addr []byte, err error) { // TODO: persist coinbase address on disk return am.firstAddr() } -// MainAccount returns the primary account used for transactions. -func (am *Manager) Default() (Account, error) { - // TODO: persist main account address on disk - addr, err := am.firstAddr() - return Account{Address: addr}, err -} - func (am *Manager) firstAddr() ([]byte, error) { addrs, err := am.keyStore.GetKeyAddresses() if err != nil { @@ -135,9 +139,7 @@ func (am *Manager) Accounts() ([]Account, error) { if err != nil { return nil, err } - accounts := make([]Account, len(addresses)) - for i, addr := range addresses { accounts[i] = Account{ Address: addr, From 697f6748b8df6b50157f81eb7959d856d3eb688e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:03:20 +0100 Subject: [PATCH 18/37] cmd/mist: use cli library and package accounts --- cmd/mist/assets/qml/views/info.qml | 41 --------- cmd/mist/bindings.go | 29 ------- cmd/mist/debugger.go | 8 +- cmd/mist/flags.go | 130 ----------------------------- cmd/mist/gui.go | 53 +----------- cmd/mist/main.go | 101 +++++++++++----------- 6 files changed, 58 insertions(+), 304 deletions(-) delete mode 100644 cmd/mist/flags.go diff --git a/cmd/mist/assets/qml/views/info.qml b/cmd/mist/assets/qml/views/info.qml index b2d2f521c..0187bba6d 100644 --- a/cmd/mist/assets/qml/views/info.qml +++ b/cmd/mist/assets/qml/views/info.qml @@ -54,7 +54,6 @@ Rectangle { height: 200 anchors { left: parent.left - right: logLevelSlider.left bottom: parent.bottom top: parent.top } @@ -107,46 +106,6 @@ Rectangle { } } } - - /* - TableView { - id: logView - headerVisible: false - anchors { - right: logLevelSlider.left - left: parent.left - bottom: parent.bottom - top: parent.top - } - - TableViewColumn{ role: "description" ; title: "log" } - - model: logModel - } - */ - - Slider { - id: logLevelSlider - value: gui.getLogLevelInt() - anchors { - right: parent.right - top: parent.top - bottom: parent.bottom - - rightMargin: 5 - leftMargin: 5 - topMargin: 5 - bottomMargin: 5 - } - - orientation: Qt.Vertical - maximumValue: 5 - stepSize: 1 - - onValueChanged: { - gui.setLogLevel(value) - } - } } property var logModel: ListModel { diff --git a/cmd/mist/bindings.go b/cmd/mist/bindings.go index fd89eb7e2..b473cc985 100644 --- a/cmd/mist/bindings.go +++ b/cmd/mist/bindings.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/state" ) @@ -37,18 +36,6 @@ type plugin struct { Path string `json:"path"` } -// LogPrint writes to the GUI log. -func (gui *Gui) LogPrint(level logger.LogLevel, msg string) { - /* - str := strings.TrimRight(s, "\n") - lines := strings.Split(str, "\n") - - view := gui.getObjectByName("infoView") - for _, line := range lines { - view.Call("addLog", line) - } - */ -} func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (string, error) { var data string if len(recipient) == 0 { @@ -64,17 +51,6 @@ func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (strin return gui.xeth.Transact(from, recipient, value, gas, gasPrice, data) } -// functions that allow Gui to implement interface guilogger.LogSystem -func (gui *Gui) SetLogLevel(level logger.LogLevel) { - gui.logLevel = level - gui.eth.Logger().SetLogLevel(level) - gui.config.Save("loglevel", level) -} - -func (gui *Gui) GetLogLevel() logger.LogLevel { - return gui.logLevel -} - func (self *Gui) AddPlugin(pluginPath string) { self.plugins[pluginPath] = plugin{Name: pluginPath, Path: pluginPath} @@ -89,11 +65,6 @@ func (self *Gui) RemovePlugin(pluginPath string) { ethutil.WriteFile(self.eth.DataDir+"/plugins.json", json) } -// this extra function needed to give int typecast value to gui widget -// that sets initial loglevel to default -func (gui *Gui) GetLogLevelInt() int { - return int(gui.logLevel) -} func (self *Gui) DumpState(hash, path string) { var stateDump []byte diff --git a/cmd/mist/debugger.go b/cmd/mist/debugger.go index c1ab2f3f1..bd8ddde37 100644 --- a/cmd/mist/debugger.go +++ b/cmd/mist/debugger.go @@ -137,16 +137,18 @@ func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, data return } + // TODO: improve this + allAccounts, _ := self.lib.eth.AccountManager().Accounts() + var ( gas = ethutil.Big(gasStr) gasPrice = ethutil.Big(gasPriceStr) value = ethutil.Big(valueStr) - // Contract addr as test address - keyPair = self.lib.eth.KeyManager().KeyPair() + acc = allAccounts[0] ) statedb := self.lib.eth.ChainManager().TransState() - account := self.lib.eth.ChainManager().TransState().GetAccount(keyPair.Address()) + account := self.lib.eth.ChainManager().TransState().GetAccount(acc.Address) contract := statedb.NewStateObject([]byte{0}) contract.SetCode(script) contract.SetBalance(value) diff --git a/cmd/mist/flags.go b/cmd/mist/flags.go deleted file mode 100644 index 139af5923..000000000 --- a/cmd/mist/flags.go +++ /dev/null @@ -1,130 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum 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. - - go-ethereum 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 go-ethereum. If not, see . -*/ -/** - * @authors - * Jeffrey Wilcke - */ -package main - -import ( - "crypto/ecdsa" - "flag" - "fmt" - "log" - "os" - "path" - "runtime" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/vm" -) - -var ( - Identifier string - KeyRing string - KeyStore string - StartRpc bool - RpcListenAddress string - RpcPort int - OutboundPort string - ShowGenesis bool - AddPeer string - MaxPeer int - GenAddr bool - BootNodes string - NodeKey *ecdsa.PrivateKey - NAT nat.Interface - SecretFile string - ExportDir string - NonInteractive bool - Datadir string - LogFile string - ConfigFile string - DebugFile string - LogLevel int - VmType int - MinerThreads int -) - -// flags specific to gui client -var AssetPath string -var defaultConfigFile = path.Join(ethutil.DefaultDataDir(), "conf.ini") - -func Init() { - // TODO: move common flag processing to cmd/utils - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s [options] [filename]:\noptions precedence: default < config file < environment variables < command line\n", os.Args[0]) - flag.PrintDefaults() - } - - flag.IntVar(&VmType, "vm", 0, "Virtual Machine type: 0-1: standard, debug") - flag.StringVar(&Identifier, "id", "", "Custom client identifier") - flag.StringVar(&KeyRing, "keyring", "", "identifier for keyring to use") - flag.StringVar(&KeyStore, "keystore", "db", "system to store keyrings: db|file") - flag.StringVar(&RpcListenAddress, "rpcaddr", "127.0.0.1", "address for json-rpc server to listen on") - flag.IntVar(&RpcPort, "rpcport", 8545, "port to start json-rpc server on") - flag.BoolVar(&StartRpc, "rpc", true, "start rpc server") - flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)") - flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") - flag.StringVar(&SecretFile, "import", "", "imports the file given (hex or mnemonic formats)") - flag.StringVar(&ExportDir, "export", "", "exports the session keyring to files in the directory given") - flag.StringVar(&LogFile, "logfile", "", "log file (defaults to standard output)") - flag.StringVar(&Datadir, "datadir", ethutil.DefaultDataDir(), "specifies the datadir to use") - flag.StringVar(&ConfigFile, "conf", defaultConfigFile, "config file") - flag.StringVar(&DebugFile, "debug", "", "debug file (no debugging if not set)") - flag.IntVar(&LogLevel, "loglevel", int(logger.InfoLevel), "loglevel: 0-5 (= silent,error,warn,info,debug,debug detail)") - - flag.StringVar(&AssetPath, "asset_path", ethutil.DefaultAssetPath(), "absolute path to GUI assets directory") - - // Network stuff - var ( - nodeKeyFile = flag.String("nodekey", "", "network private key file") - nodeKeyHex = flag.String("nodekeyhex", "", "network private key (for testing)") - natstr = flag.String("nat", "any", "port mapping mechanism (any|none|upnp|pmp|extip:)") - ) - flag.StringVar(&OutboundPort, "port", "30303", "listening port") - flag.StringVar(&BootNodes, "bootnodes", "", "space-separated node URLs for discovery bootstrap") - flag.IntVar(&MaxPeer, "maxpeer", 30, "maximum desired peers") - - flag.IntVar(&MinerThreads, "minerthreads", runtime.NumCPU(), "number of miner threads") - - flag.Parse() - - var err error - if NAT, err = nat.Parse(*natstr); err != nil { - log.Fatalf("-nat: %v", err) - } - switch { - case *nodeKeyFile != "" && *nodeKeyHex != "": - log.Fatal("Options -nodekey and -nodekeyhex are mutually exclusive") - case *nodeKeyFile != "": - if NodeKey, err = crypto.LoadECDSA(*nodeKeyFile); err != nil { - log.Fatalf("-nodekey: %v", err) - } - case *nodeKeyHex != "": - if NodeKey, err = crypto.HexToECDSA(*nodeKeyHex); err != nil { - log.Fatalf("-nodekeyhex: %v", err) - } - } - - if VmType >= int(vm.MaxVmTy) { - log.Fatal("Invalid VM type ", VmType) - } -} diff --git a/cmd/mist/gui.go b/cmd/mist/gui.go index bc6e9ed53..e21d6fcea 100644 --- a/cmd/mist/gui.go +++ b/cmd/mist/gui.go @@ -23,7 +23,6 @@ package main import "C" import ( - "bytes" "encoding/json" "fmt" "io/ioutil" @@ -70,20 +69,18 @@ type Gui struct { txDb *ethdb.LDBDatabase - logLevel logger.LogLevel - open bool + open bool xeth *xeth.XEth Session string - config *ethutil.ConfigManager plugins map[string]plugin } // Create GUI, but doesn't start it -func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session string, logLevel int) *Gui { - db, err := ethdb.NewLDBDatabase("tx_database") +func NewWindow(ethereum *eth.Ethereum) *Gui { + db, err := ethdb.NewLDBDatabase(path.Join(ethereum.DataDir, "tx_database")) if err != nil { panic(err) } @@ -92,10 +89,7 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session st gui := &Gui{eth: ethereum, txDb: db, xeth: xeth, - logLevel: logger.LogLevel(logLevel), - Session: session, open: false, - config: config, plugins: make(map[string]plugin), serviceEvents: make(chan ServEv, 1), } @@ -142,18 +136,12 @@ func (gui *Gui) Start(assetPath string) { gui.open = true win.Show() - // only add the gui guilogger after window is shown otherwise slider wont be shown - logger.AddLogSystem(gui) win.Wait() - - // need to silence gui guilogger after window closed otherwise logsystem hangs (but do not save loglevel) - gui.logLevel = logger.Silence gui.open = false } func (gui *Gui) Stop() { if gui.open { - gui.logLevel = logger.Silence gui.open = false gui.win.Hide() } @@ -172,9 +160,6 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { return gui.win, nil } -func (gui *Gui) ImportKey(filePath string) { -} - func (gui *Gui) GenerateKey() { _, err := gui.eth.AccountManager().NewAccount("hurr") if err != nil { @@ -198,31 +183,11 @@ func (gui *Gui) createWindow(comp qml.Object) *qml.Window { return gui.win } -func (gui *Gui) ImportAndSetPrivKey(secret string) bool { - err := gui.eth.KeyManager().InitFromString(gui.Session, 0, secret) - if err != nil { - guilogger.Errorln("unable to import: ", err) - return false - } - guilogger.Errorln("successfully imported: ", err) - return true -} - -func (gui *Gui) CreateAndSetPrivKey() (string, string, string, string) { - err := gui.eth.KeyManager().Init(gui.Session, 0, true) - if err != nil { - guilogger.Errorln("unable to create key: ", err) - return "", "", "", "" - } - return gui.eth.KeyManager().KeyPair().AsStrings() -} - func (gui *Gui) setInitialChain(ancientBlocks bool) { sBlk := gui.eth.ChainManager().LastBlockHash() blk := gui.eth.ChainManager().GetBlock(sBlk) for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) { sBlk = blk.ParentHash() - gui.processBlock(blk, true) } } @@ -266,10 +231,8 @@ func (self *Gui) loadMergedMiningOptions() { } func (gui *Gui) insertTransaction(window string, tx *types.Transaction) { - addr := gui.address() - var inout string - if bytes.Compare(tx.From(), addr) == 0 { + if gui.eth.AccountManager().HasAccount(tx.From()) { inout = "send" } else { inout = "recv" @@ -487,14 +450,6 @@ func (gui *Gui) setPeerInfo() { } } -func (gui *Gui) privateKey() string { - return ethutil.Bytes2Hex(gui.eth.KeyManager().PrivateKey()) -} - -func (gui *Gui) address() []byte { - return gui.eth.KeyManager().Address() -} - /* func LoadExtension(path string) (uintptr, error) { lib, err := ffi.NewLibrary(path) diff --git a/cmd/mist/main.go b/cmd/mist/main.go index 1d4403848..425630ece 100644 --- a/cmd/mist/main.go +++ b/cmd/mist/main.go @@ -26,10 +26,10 @@ import ( "runtime" "time" + "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/ui/qt/webengine" "github.com/obscuren/qml" ) @@ -39,56 +39,32 @@ const ( Version = "0.8.6" ) -var ethereum *eth.Ethereum -var mainlogger = logger.NewLogger("MAIN") - -func run() error { - webengine.Initialize() - - // precedence: code-internal flag default < config file < environment variables < command line - Init() // parsing command line - - tstart := time.Now() - config := utils.InitConfig(VmType, ConfigFile, Datadir, "ETH") - - ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(ClientIdentifier, Version), - KeyStore: KeyStore, - DataDir: Datadir, - LogFile: LogFile, - LogLevel: LogLevel, - MaxPeers: MaxPeer, - Port: OutboundPort, - NAT: NAT, - Shh: true, - BootNodes: BootNodes, - NodeKey: NodeKey, - KeyRing: KeyRing, - Dial: true, - MinerThreads: MinerThreads, - }) - if err != nil { - mainlogger.Fatalln(err) +var ( + app = utils.NewApp(Version, "the ether browser") + assetPathFlag = cli.StringFlag{ + Name: "asset_path", + Usage: "absolute path to GUI assets directory", + Value: ethutil.DefaultAssetPath(), } - utils.KeyTasks(ethereum.KeyManager(), KeyRing, GenAddr, SecretFile, ExportDir, NonInteractive) +) - if StartRpc { - utils.StartRpc(ethereum, RpcListenAddress, RpcPort) +func init() { + app.Action = run + app.Flags = []cli.Flag{ + assetPathFlag, + + utils.BootnodesFlag, + utils.DataDirFlag, + utils.ListenPortFlag, + utils.LogFileFlag, + utils.LogLevelFlag, + utils.MaxPeersFlag, + utils.MinerThreadsFlag, + utils.NATFlag, + utils.NodeKeyFileFlag, + utils.RPCListenAddrFlag, + utils.RPCPortFlag, } - - gui := NewWindow(ethereum, config, KeyRing, LogLevel) - - utils.RegisterInterrupt(func(os.Signal) { - gui.Stop() - }) - go utils.StartEthereum(ethereum) - - fmt.Println("ETH stack took", time.Since(tstart)) - - // gui blocks the main thread - gui.Start(AssetPath) - - return nil } func main() { @@ -97,15 +73,16 @@ func main() { // This is a bit of a cheat, but ey! os.Setenv("QTWEBKIT_INSPECTOR_SERVER", "127.0.0.1:99999") - qml.Run(run) - var interrupted = false utils.RegisterInterrupt(func(os.Signal) { interrupted = true }) - utils.HandleInterrupt() + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, "Error: ", err) + } + // we need to run the interrupt callbacks in case gui is closed // this skips if we got here by actual interrupt stopping the GUI if !interrupted { @@ -113,3 +90,23 @@ func main() { } logger.Flush() } + +func run(ctx *cli.Context) { + tstart := time.Now() + + // TODO: show qml popup instead of exiting if initialization fails. + ethereum := utils.GetEthereum(ClientIdentifier, Version, ctx) + utils.StartRPC(ethereum, ctx) + go utils.StartEthereum(ethereum) + fmt.Println("initializing eth stack took", time.Since(tstart)) + + // Open the window + qml.Run(func() error { + webengine.Initialize() + gui := NewWindow(ethereum) + utils.RegisterInterrupt(func(os.Signal) { gui.Stop() }) + // gui blocks the main thread + gui.Start(ctx.GlobalString(assetPathFlag.Name)) + return nil + }) +} From cd51860bf024d949a09b3863c88a278a386227a7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:06:04 +0100 Subject: [PATCH 19/37] cmd/utils: delete InitConfig, KeyTasks --- cmd/utils/cmd.go | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 79ae7888a..271a879c7 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" @@ -96,14 +95,6 @@ func initDataDir(Datadir string) { } } -func InitConfig(vmType int, ConfigFile string, Datadir string, EnvPrefix string) *ethutil.ConfigManager { - initDataDir(Datadir) - cfg := ethutil.ReadConfig(ConfigFile, Datadir, EnvPrefix) - cfg.VmType = vmType - - return cfg -} - func exit(err error) { status := 0 if err != nil { @@ -132,37 +123,6 @@ func StartEthereum(ethereum *eth.Ethereum) { }) } -func KeyTasks(keyManager *crypto.KeyManager, KeyRing string, GenAddr bool, SecretFile string, ExportDir string, NonInteractive bool) { - var err error - switch { - case GenAddr: - if NonInteractive || confirm("This action overwrites your old private key.") { - err = keyManager.Init(KeyRing, 0, true) - } - exit(err) - case len(SecretFile) > 0: - SecretFile = ethutil.ExpandHomePath(SecretFile) - - if NonInteractive || confirm("This action overwrites your old private key.") { - err = keyManager.InitFromSecretsFile(KeyRing, 0, SecretFile) - } - exit(err) - case len(ExportDir) > 0: - err = keyManager.Init(KeyRing, 0, false) - if err == nil { - err = keyManager.Export(ExportDir) - } - exit(err) - default: - // Creates a keypair if none exists - err = keyManager.Init(KeyRing, 0, false) - if err != nil { - exit(err) - } - } - clilogger.Infof("Main address %x\n", keyManager.Address()) -} - func FormatTransactionData(data string) []byte { d := ethutil.StringToByteFunc(data, func(s string) (ret []byte) { slice := regexp.MustCompile("\\n|\\s").Split(s, 1000000000) From 487f68ec4892794cb994cffd95d5bc2bf3052d3e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 00:09:39 +0100 Subject: [PATCH 20/37] accounts: add {Timed,}Unlock, remove SignLocked --- accounts/account_manager.go | 48 ++++++++++++++++++---------- accounts/accounts_test.go | 64 +++++++++++++++---------------------- cmd/utils/flags.go | 3 +- 3 files changed, 57 insertions(+), 58 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 4575334bf..fdd7d83e9 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -54,10 +54,9 @@ type Account struct { } type Manager struct { - keyStore crypto.KeyStore2 - unlocked map[string]*unlocked - unlockTime time.Duration - mutex sync.RWMutex + keyStore crypto.KeyStore2 + unlocked map[string]*unlocked + mutex sync.RWMutex } type unlocked struct { @@ -65,11 +64,10 @@ type unlocked struct { abort chan struct{} } -func NewManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *Manager { +func NewManager(keyStore crypto.KeyStore2) *Manager { return &Manager{ - keyStore: keyStore, - unlocked: make(map[string]*unlocked), - unlockTime: unlockTime, + keyStore: keyStore, + unlocked: make(map[string]*unlocked), } } @@ -115,15 +113,28 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) return signature, err } -func (am *Manager) SignLocked(a Account, keyAuth string, toSign []byte) (signature []byte, err error) { - key, err := am.keyStore.GetKey(a.Address, keyAuth) +// TimedUnlock unlocks the account with the given address. +// When timeout has passed, the account will be locked again. +func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error { + key, err := am.keyStore.GetKey(addr, keyAuth) if err != nil { - return nil, err + return err } - u := am.addUnlocked(a.Address, key) - go am.dropLater(a.Address, u) - signature, err = crypto.Sign(toSign, key.PrivateKey) - return signature, err + u := am.addUnlocked(addr, key) + go am.dropLater(addr, u, timeout) + return nil +} + +// Unlock unlocks the account with the given address. The account +// stays unlocked until the program exits or until a TimedUnlock +// timeout (started after the call to Unlock) expires. +func (am *Manager) Unlock(addr []byte, keyAuth string) error { + key, err := am.keyStore.GetKey(addr, keyAuth) + if err != nil { + return err + } + am.addUnlocked(addr, key) + return nil } func (am *Manager) NewAccount(auth string) (Account, error) { @@ -155,6 +166,9 @@ func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { if found { // terminate dropLater for this key to avoid unexpected drops. close(prev.abort) + // the key is zeroed here instead of in dropLater because + // there might not actually be a dropLater running for this + // key, i.e. when Unlock was used. zeroKey(prev.PrivateKey) } am.unlocked[string(addr)] = u @@ -162,8 +176,8 @@ func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { return u } -func (am *Manager) dropLater(addr []byte, u *unlocked) { - t := time.NewTimer(am.unlockTime) +func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) { + t := time.NewTimer(timeout) defer t.Stop() select { case <-u.abort: diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index b90da2892..427114cbd 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -1,44 +1,36 @@ package accounts import ( + "io/ioutil" + "os" "testing" - "time" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" - "github.com/ethereum/go-ethereum/ethutil" ) -func TestAccountManager(t *testing.T) { - ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") - am := NewManager(ks, 100*time.Millisecond) +func TestSign(t *testing.T) { + dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain) + defer os.RemoveAll(dir) + + am := NewManager(ks) pass := "" // not used but required by API a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) - _, err = am.SignLocked(a1, pass, toSign) + am.Unlock(a1.Address, "") + + _, err = am.Sign(a1, toSign) if err != nil { t.Fatal(err) } - - // Cleanup - time.Sleep(150 * time.Millisecond) // wait for locking - - accounts, err := am.Accounts() - if err != nil { - t.Fatal(err) - } - for _, account := range accounts { - err := am.DeleteAccount(account.Address, pass) - if err != nil { - t.Fatal(err) - } - } } -func TestAccountManagerLocking(t *testing.T) { - ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts") - am := NewManager(ks, 200*time.Millisecond) +func TestTimedUnlock(t *testing.T) { + dir, ks := tmpKeyStore(t, crypto.NewKeyStorePassphrase) + defer os.RemoveAll(dir) + + am := NewManager(ks) pass := "foo" a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -46,38 +38,32 @@ func TestAccountManagerLocking(t *testing.T) { // Signing without passphrase fails because account is locked _, err = am.Sign(a1, toSign) if err != ErrLocked { - t.Fatal(err) + t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) } // Signing with passphrase works - _, err = am.SignLocked(a1, pass, toSign) - if err != nil { + if err = am.TimedUnlock(a1.Address, pass, 100*time.Millisecond); err != nil { t.Fatal(err) } // Signing without passphrase works because account is temp unlocked _, err = am.Sign(a1, toSign) if err != nil { - t.Fatal(err) + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) } - // Signing without passphrase fails after automatic locking - time.Sleep(250 * time.Millisecond) - + // Signing fails again after automatic locking + time.Sleep(150 * time.Millisecond) _, err = am.Sign(a1, toSign) if err != ErrLocked { - t.Fatal(err) + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) } +} - // Cleanup - accounts, err := am.Accounts() +func tmpKeyStore(t *testing.T, new func(string) crypto.KeyStore2) (string, crypto.KeyStore2) { + d, err := ioutil.TempDir("", "eth-keystore-test") if err != nil { t.Fatal(err) } - for _, account := range accounts { - err := am.DeleteAccount(account.Address, pass) - if err != nil { - t.Fatal(err) - } - } + return d, new(d) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ee7ea4c79..cde5fa024 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -8,7 +8,6 @@ import ( "os" "path" "runtime" - "time" "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/accounts" @@ -199,7 +198,7 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.D func GetAccountManager(ctx *cli.Context) *accounts.Manager { dataDir := ctx.GlobalString(DataDirFlag.Name) ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) - return accounts.NewManager(ks, 300*time.Second) + return accounts.NewManager(ks) } func StartRPC(eth *eth.Ethereum, ctx *cli.Context) { From d1e04f7388ec29b009d646e20570393a5851f54d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 00:24:11 +0100 Subject: [PATCH 21/37] cmd/ethereum: allow multiple js files --- cmd/ethereum/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 1ae8e46a2..d0edef81d 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -159,13 +159,13 @@ func runjs(ctx *cli.Context) { startEth(ctx, eth) if len(ctx.Args()) == 0 { runREPL(eth) - eth.Stop() - eth.WaitForShutdown() - } else if len(ctx.Args()) == 1 { - execJsFile(eth, ctx.Args()[0]) } else { - utils.Fatalf("This command can handle at most one argument.") + for _, file := range ctx.Args() { + execJsFile(eth, file) + } } + eth.Stop() + eth.WaitForShutdown() } func startEth(ctx *cli.Context, eth *eth.Ethereum) { From 395da0e7c1accfaeda6527a473e9a31b11fe88a8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:00:06 +0100 Subject: [PATCH 22/37] xeth: use Frontend interface to unlock accounts The interface has moved to package xeth because that's where it is actually used. --- ui/frontend.go | 8 ------ xeth/xeth.go | 78 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 55 insertions(+), 31 deletions(-) delete mode 100644 ui/frontend.go diff --git a/ui/frontend.go b/ui/frontend.go deleted file mode 100644 index 413a24259..000000000 --- a/ui/frontend.go +++ /dev/null @@ -1,8 +0,0 @@ -package ui - -import "github.com/ethereum/go-ethereum/core/types" - -type Interface interface { - UnlockAccount(address []byte) bool - ConfirmTransaction(tx *types.Transaction) bool -} diff --git a/xeth/xeth.go b/xeth/xeth.go index a0491506b..f8b537321 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -1,12 +1,10 @@ +// eXtended ETHereum package xeth -/* - * eXtended ETHereum - */ - import ( "bytes" "encoding/json" + "fmt" "math/big" "github.com/ethereum/go-ethereum/accounts" @@ -19,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/state" - "github.com/ethereum/go-ethereum/ui" "github.com/ethereum/go-ethereum/whisper" ) @@ -41,6 +38,26 @@ type Backend interface { Miner() *miner.Miner } +// Frontend should be implemented by users of XEth. Its methods are +// called whenever XEth makes a decision that requires user input. +type Frontend interface { + // UnlockAccount is called when a transaction needs to be signed + // but the key corresponding to the transaction's sender is + // locked. + // + // It should unlock the account with the given address and return + // true if unlocking succeeded. + UnlockAccount(address []byte) bool + + // This is called for all transactions inititated through + // Transact. It should prompt the user to confirm the transaction + // and return true if the transaction was acknowledged. + // + // ConfirmTransaction is not used for Call transactions + // because they cannot change any state. + ConfirmTransaction(tx *types.Transaction) bool +} + type XEth struct { eth Backend blockProcessor *core.BlockProcessor @@ -50,15 +67,20 @@ type XEth struct { whisper *Whisper miner *miner.Miner - frontend ui.Interface + frontend Frontend } -type TmpFrontend struct{} +// dummyFrontend is a non-interactive frontend that allows all +// transactions but cannot not unlock any keys. +type dummyFrontend struct{} -func (TmpFrontend) UnlockAccount([]byte) bool { panic("UNLOCK ACCOUNT") } -func (TmpFrontend) ConfirmTransaction(*types.Transaction) bool { panic("CONFIRM TRANSACTION") } +func (dummyFrontend) UnlockAccount([]byte) bool { return false } +func (dummyFrontend) ConfirmTransaction(*types.Transaction) bool { return true } -func New(eth Backend, frontend ui.Interface) *XEth { +// New creates an XEth that uses the given frontend. +// If a nil Frontend is provided, a default frontend which +// confirms all transactions will be used. +func New(eth Backend, frontend Frontend) *XEth { xeth := &XEth{ eth: eth, blockProcessor: eth.BlockProcessor(), @@ -66,14 +88,12 @@ func New(eth Backend, frontend ui.Interface) *XEth { accountManager: eth.AccountManager(), whisper: NewWhisper(eth.Whisper()), miner: eth.Miner(), + frontend: frontend, } - if frontend == nil { - xeth.frontend = TmpFrontend{} + xeth.frontend = dummyFrontend{} } - xeth.state = NewState(xeth, xeth.chainManager.TransState()) - return xeth } @@ -283,7 +303,6 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st } func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { - var ( from []byte to []byte @@ -310,16 +329,12 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt state := self.chainManager.TransState() nonce := state.GetNonce(from) - tx.SetNonce(nonce) - sig, err := self.accountManager.Sign(accounts.Account{Address: from}, tx.Hash()) - if err != nil { + + if err := self.sign(tx, from, false); err != nil { return "", err } - tx.SetSignatureValues(sig) - - err = self.eth.TxPool().Add(tx) - if err != nil { + if err := self.eth.TxPool().Add(tx); err != nil { return "", err } state.SetNonce(from, nonce+1) @@ -332,10 +347,27 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt if types.IsContractAddr(to) { return toHex(core.AddressFromMessage(tx)), nil } - return toHex(tx.Hash()), nil } +func (self *XEth) sign(tx *types.Transaction, from []byte, didUnlock bool) error { + sig, err := self.accountManager.Sign(accounts.Account{Address: from}, tx.Hash()) + if err == accounts.ErrLocked { + if didUnlock { + return fmt.Errorf("sender account still locked after successful unlock") + } + if !self.frontend.UnlockAccount(from) { + return fmt.Errorf("could not unlock sender account") + } + // retry signing, the account should now be unlocked. + self.sign(tx, from, true) + } else if err != nil { + return err + } + tx.SetSignatureValues(sig) + return nil +} + // callmsg is the message type used for call transations. type callmsg struct { from *state.StateObject From 9f0e3bd286472f85ab2457fc19cd48cdf12df110 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:00:57 +0100 Subject: [PATCH 23/37] cmd/ethereum: unlock accounts on JS REPL --- cmd/ethereum/js.go | 212 ++++++++++++++++++++++--------------------- cmd/ethereum/main.go | 5 +- 2 files changed, 112 insertions(+), 105 deletions(-) diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index e3165d3f5..96e292733 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -22,11 +22,9 @@ import ( "fmt" "io/ioutil" "os" - "os/signal" "path" "strings" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethutil" @@ -37,51 +35,117 @@ import ( "github.com/peterh/liner" ) -func execJsFile(ethereum *eth.Ethereum, filename string) { - file, err := os.Open(filename) - if err != nil { - utils.Fatalf("%v", err) - } - content, err := ioutil.ReadAll(file) - if err != nil { - utils.Fatalf("%v", err) - } - re := javascript.NewJSRE(xeth.New(ethereum, nil)) - if _, err := re.Run(string(content)); err != nil { - utils.Fatalf("Javascript Error: %v", err) - } +type prompter interface { + AppendHistory(string) + Prompt(p string) (string, error) + PasswordPrompt(p string) (string, error) } -type repl struct { +type dumbPrompter struct{ r *bufio.Reader } + +func (r dumbPrompter) Prompt(p string) (string, error) { + fmt.Print(p) + return r.r.ReadString('\n') +} + +func (r dumbPrompter) PasswordPrompt(p string) (string, error) { + fmt.Println("!! Unsupported terminal, password will echo.") + fmt.Print(p) + input, err := bufio.NewReader(os.Stdin).ReadString('\n') + fmt.Println() + return input, err +} + +func (r dumbPrompter) AppendHistory(string) {} + +type jsre struct { re *javascript.JSRE ethereum *eth.Ethereum xeth *xeth.XEth - prompt string - lr *liner.State + ps1 string + prompter } -func runREPL(ethereum *eth.Ethereum) { - xeth := xeth.New(ethereum, nil) - repl := &repl{ - re: javascript.NewJSRE(xeth), - xeth: xeth, - ethereum: ethereum, - prompt: "> ", - } - repl.initStdFuncs() +func newJSRE(ethereum *eth.Ethereum) *jsre { + js := &jsre{ethereum: ethereum, ps1: "> "} + js.xeth = xeth.New(ethereum, js) + js.re = javascript.NewJSRE(js.xeth) + js.initStdFuncs() + if !liner.TerminalSupported() { - repl.dumbRead() + js.prompter = dumbPrompter{bufio.NewReader(os.Stdin)} } else { lr := liner.NewLiner() - defer lr.Close() lr.SetCtrlCAborts(true) - repl.withHistory(func(hist *os.File) { lr.ReadHistory(hist) }) - repl.read(lr) - repl.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) + defer lr.Close() + js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) }) + defer js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) + js.prompter = lr + } + return js +} + +func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool { + p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx) + answer, _ := self.prompter.Prompt(p) + return strings.HasPrefix(strings.Trim(answer, " "), "y") +} + +func (self *jsre) UnlockAccount(addr []byte) bool { + fmt.Printf("Please unlock account %x.\n", addr) + pass, err := self.prompter.PasswordPrompt("Passphrase: ") + if err != nil { + return false + } + // TODO: allow retry + if err := self.ethereum.AccountManager().Unlock(addr, pass); err != nil { + fmt.Println("Unlocking failed: ", err) + return false + } else { + fmt.Println("Account is now unlocked for this session.") + return true } } -func (self *repl) withHistory(op func(*os.File)) { +func (self *jsre) exec(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + content, err := ioutil.ReadAll(file) + if err != nil { + return err + } + if _, err := self.re.Run(string(content)); err != nil { + return fmt.Errorf("Javascript Error: %v", err) + } + return nil +} + +func (self *jsre) interactive() { + for { + input, err := self.prompter.Prompt(self.ps1) + if err != nil { + return + } + if input == "" { + continue + } + str += input + "\n" + self.setIndent() + if indentCount <= 0 { + if input == "exit" { + return + } + hist := str[:len(str)-1] + self.prompter.AppendHistory(hist) + self.parseInput(str) + str = "" + } + } +} + +func (self *jsre) withHistory(op func(*os.File)) { hist, err := os.OpenFile(path.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { fmt.Printf("unable to open history file: %v\n", err) @@ -91,7 +155,7 @@ func (self *repl) withHistory(op func(*os.File)) { hist.Close() } -func (self *repl) parseInput(code string) { +func (self *jsre) parseInput(code string) { defer func() { if r := recover(); r != nil { fmt.Println("[native] error", r) @@ -108,79 +172,21 @@ func (self *repl) parseInput(code string) { var indentCount = 0 var str = "" -func (self *repl) setIndent() { +func (self *jsre) setIndent() { open := strings.Count(str, "{") open += strings.Count(str, "(") closed := strings.Count(str, "}") closed += strings.Count(str, ")") indentCount = open - closed if indentCount <= 0 { - self.prompt = "> " + self.ps1 = "> " } else { - self.prompt = strings.Join(make([]string, indentCount*2), "..") - self.prompt += " " + self.ps1 = strings.Join(make([]string, indentCount*2), "..") + self.ps1 += " " } } -func (self *repl) read(lr *liner.State) { - for { - input, err := lr.Prompt(self.prompt) - if err != nil { - return - } - if input == "" { - continue - } - str += input + "\n" - self.setIndent() - if indentCount <= 0 { - if input == "exit" { - return - } - hist := str[:len(str)-1] - lr.AppendHistory(hist) - self.parseInput(str) - str = "" - } - } -} - -func (self *repl) dumbRead() { - fmt.Println("Unsupported terminal, line editing will not work.") - - // process lines - readDone := make(chan struct{}) - go func() { - r := bufio.NewReader(os.Stdin) - loop: - for { - fmt.Print(self.prompt) - line, err := r.ReadString('\n') - switch { - case err != nil || line == "exit": - break loop - case line == "": - continue - default: - self.parseInput(line + "\n") - } - } - close(readDone) - }() - - // wait for Ctrl-C - sigc := make(chan os.Signal, 1) - signal.Notify(sigc, os.Interrupt, os.Kill) - defer signal.Stop(sigc) - - select { - case <-readDone: - case <-sigc: - os.Stdin.Close() // terminate read - } -} - -func (self *repl) printValue(v interface{}) { +func (self *jsre) printValue(v interface{}) { method, _ := self.re.Vm.Get("prettyPrint") v, err := self.re.Vm.ToValue(v) if err == nil { @@ -191,7 +197,7 @@ func (self *repl) printValue(v interface{}) { } } -func (self *repl) initStdFuncs() { +func (self *jsre) initStdFuncs() { t, _ := self.re.Vm.Get("eth") eth := t.Object() eth.Set("connect", self.connect) @@ -205,7 +211,7 @@ func (self *repl) initStdFuncs() { * The following methods are natively implemented javascript functions. */ -func (self *repl) dump(call otto.FunctionCall) otto.Value { +func (self *jsre) dump(call otto.FunctionCall) otto.Value { var block *types.Block if len(call.ArgumentList) > 0 { @@ -236,17 +242,17 @@ func (self *repl) dump(call otto.FunctionCall) otto.Value { return v } -func (self *repl) stopMining(call otto.FunctionCall) otto.Value { +func (self *jsre) stopMining(call otto.FunctionCall) otto.Value { self.xeth.Miner().Stop() return otto.TrueValue() } -func (self *repl) startMining(call otto.FunctionCall) otto.Value { +func (self *jsre) startMining(call otto.FunctionCall) otto.Value { self.xeth.Miner().Start() return otto.TrueValue() } -func (self *repl) connect(call otto.FunctionCall) otto.Value { +func (self *jsre) connect(call otto.FunctionCall) otto.Value { nodeURL, err := call.Argument(0).ToString() if err != nil { return otto.FalseValue() @@ -257,7 +263,7 @@ func (self *repl) connect(call otto.FunctionCall) otto.Value { return otto.TrueValue() } -func (self *repl) export(call otto.FunctionCall) otto.Value { +func (self *jsre) export(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) == 0 { fmt.Println("err: require file name") return otto.FalseValue() diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index d0edef81d..1703c02bb 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -157,11 +157,12 @@ func run(ctx *cli.Context) { func runjs(ctx *cli.Context) { eth := utils.GetEthereum(ClientIdentifier, Version, ctx) startEth(ctx, eth) + repl := newJSRE(eth) if len(ctx.Args()) == 0 { - runREPL(eth) + repl.interactive() } else { for _, file := range ctx.Args() { - execJsFile(eth, file) + repl.exec(file) } } eth.Stop() From 62ebce304edcdd9d0c1022b57d9e744013c0818f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:12:55 +0100 Subject: [PATCH 24/37] crypto: delete old key management stuff --- crypto/key_manager.go | 134 ------------------------------------------ crypto/key_store.go | 113 ----------------------------------- crypto/keyring.go | 123 -------------------------------------- crypto/keys_test.go | 122 -------------------------------------- 4 files changed, 492 deletions(-) delete mode 100644 crypto/key_manager.go delete mode 100644 crypto/key_store.go delete mode 100644 crypto/keyring.go delete mode 100644 crypto/keys_test.go diff --git a/crypto/key_manager.go b/crypto/key_manager.go deleted file mode 100644 index 326e559e0..000000000 --- a/crypto/key_manager.go +++ /dev/null @@ -1,134 +0,0 @@ -package crypto - -import ( - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" -) - -var keylogger = logger.NewLogger("KEY") - -type KeyManager struct { - keyRing *KeyRing - session string - keyStore KeyStore // interface - keyRings map[string]*KeyRing // cache - keyPair *KeyPair -} - -func NewDBKeyManager(db ethutil.Database) *KeyManager { - return &KeyManager{keyStore: &DBKeyStore{db: db}, keyRings: make(map[string]*KeyRing)} -} - -func NewFileKeyManager(basedir string) *KeyManager { - return &KeyManager{keyStore: &FileKeyStore{basedir: basedir}, keyRings: make(map[string]*KeyRing)} -} - -func (k *KeyManager) KeyPair() *KeyPair { - return k.keyPair -} - -func (k *KeyManager) KeyRing() *KeyPair { - return k.keyPair -} - -func (k *KeyManager) PrivateKey() []byte { - return k.keyPair.PrivateKey -} - -func (k *KeyManager) PublicKey() []byte { - return k.keyPair.PublicKey -} - -func (k *KeyManager) Address() []byte { - return k.keyPair.Address() -} - -func (k *KeyManager) save(session string, keyRing *KeyRing) error { - err := k.keyStore.Save(session, keyRing) - if err != nil { - return err - } - k.keyRings[session] = keyRing - return nil -} - -func (k *KeyManager) load(session string) (*KeyRing, error) { - keyRing, found := k.keyRings[session] - if !found { - var err error - keyRing, err = k.keyStore.Load(session) - if err != nil { - return nil, err - } - } - return keyRing, nil -} - -func cursorError(cursor int, len int) error { - return fmt.Errorf("cursor %d out of range (0..%d)", cursor, len) -} - -func (k *KeyManager) reset(session string, cursor int, keyRing *KeyRing) error { - if cursor >= keyRing.Len() { - return cursorError(cursor, keyRing.Len()) - } - lock := &sync.Mutex{} - lock.Lock() - defer lock.Unlock() - err := k.save(session, keyRing) - if err != nil { - return err - } - k.session = session - k.keyRing = keyRing - k.keyPair = keyRing.GetKeyPair(cursor) - return nil -} - -func (k *KeyManager) SetCursor(cursor int) error { - if cursor >= k.keyRing.Len() { - return cursorError(cursor, k.keyRing.Len()) - } - k.keyPair = k.keyRing.GetKeyPair(cursor) - return nil -} - -func (k *KeyManager) Init(session string, cursor int, force bool) error { - var keyRing *KeyRing - if !force { - var err error - keyRing, err = k.load(session) - if err != nil { - return err - } - } - if keyRing == nil { - keyRing = NewGeneratedKeyRing(1) - keylogger.Infof("Created keypair. Private key: %x\n", keyRing.keys[0].PrivateKey) - } - return k.reset(session, cursor, keyRing) -} - -func (k *KeyManager) InitFromSecretsFile(session string, cursor int, secretsfile string) error { - keyRing, err := NewKeyRingFromFile(secretsfile) - if err != nil { - return err - } - return k.reset(session, cursor, keyRing) -} - -func (k *KeyManager) InitFromString(session string, cursor int, secrets string) error { - keyRing, err := NewKeyRingFromString(secrets) - if err != nil { - return err - } - return k.reset(session, cursor, keyRing) -} - -func (k *KeyManager) Export(dir string) error { - fileKeyStore := FileKeyStore{dir} - return fileKeyStore.Save(k.session, k.keyRing) -} diff --git a/crypto/key_store.go b/crypto/key_store.go deleted file mode 100644 index 04560a04e..000000000 --- a/crypto/key_store.go +++ /dev/null @@ -1,113 +0,0 @@ -package crypto - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - "github.com/ethereum/go-ethereum/ethutil" -) - -type KeyStore interface { - Load(string) (*KeyRing, error) - Save(string, *KeyRing) error -} - -type DBKeyStore struct { - db ethutil.Database -} - -const dbKeyPrefix = "KeyRing" - -func (k *DBKeyStore) dbKey(session string) []byte { - return []byte(fmt.Sprintf("%s%s", dbKeyPrefix, session)) -} - -func (k *DBKeyStore) Save(session string, keyRing *KeyRing) error { - k.db.Put(k.dbKey(session), keyRing.RlpEncode()) - return nil -} - -func (k *DBKeyStore) Load(session string) (*KeyRing, error) { - data, err := k.db.Get(k.dbKey(session)) - if err != nil { - return nil, nil - } - var keyRing *KeyRing - keyRing, err = NewKeyRingFromBytes(data) - if err != nil { - return nil, err - } - // if empty keyRing is found we return nil, no error - if keyRing.Len() == 0 { - return nil, nil - } - return keyRing, nil -} - -type FileKeyStore struct { - basedir string -} - -func (k *FileKeyStore) Save(session string, keyRing *KeyRing) error { - var content []byte - var err error - var privateKeys []string - var publicKeys []string - var mnemonics []string - var addresses []string - keyRing.Each(func(keyPair *KeyPair) { - privateKeys = append(privateKeys, ethutil.Bytes2Hex(keyPair.PrivateKey)) - publicKeys = append(publicKeys, ethutil.Bytes2Hex(keyPair.PublicKey)) - addresses = append(addresses, ethutil.Bytes2Hex(keyPair.Address())) - mnemonics = append(mnemonics, keyPair.Mnemonic()) - }) - - basename := session - if session == "" { - basename = "default" - } - - path := path.Join(k.basedir, basename) - content = []byte(strings.Join(privateKeys, "\n")) - err = ioutil.WriteFile(path+".prv", content, 0600) - if err != nil { - return err - } - - content = []byte(strings.Join(publicKeys, "\n")) - err = ioutil.WriteFile(path+".pub", content, 0644) - if err != nil { - return err - } - - content = []byte(strings.Join(addresses, "\n")) - err = ioutil.WriteFile(path+".addr", content, 0644) - if err != nil { - return err - } - - content = []byte(strings.Join(mnemonics, "\n")) - err = ioutil.WriteFile(path+".mne", content, 0600) - if err != nil { - return err - } - - return nil -} - -func (k *FileKeyStore) Load(session string) (*KeyRing, error) { - basename := session - if session == "" { - basename = "default" - } - secfile := path.Join(k.basedir, basename+".prv") - _, err := os.Stat(secfile) - // if file is not found then we return nil, no error - if err != nil { - return nil, nil - } - return NewKeyRingFromFile(secfile) -} diff --git a/crypto/keyring.go b/crypto/keyring.go deleted file mode 100644 index eab13dbc4..000000000 --- a/crypto/keyring.go +++ /dev/null @@ -1,123 +0,0 @@ -package crypto - -import ( - "fmt" - "io/ioutil" - "strings" - - "github.com/ethereum/go-ethereum/ethutil" -) - -type KeyRing struct { - keys []*KeyPair -} - -func NewKeyRing() *KeyRing { - return &KeyRing{} -} - -func (k *KeyRing) AddKeyPair(keyPair *KeyPair) { - k.keys = append(k.keys, keyPair) -} - -func (k *KeyRing) GetKeyPair(i int) *KeyPair { - if len(k.keys) > i { - return k.keys[i] - } - - return nil -} - -func (k *KeyRing) Empty() bool { - return k.Len() == 0 -} - -func (k *KeyRing) Len() int { - return len(k.keys) -} - -func (k *KeyRing) Each(f func(*KeyPair)) { - for _, keyPair := range k.keys { - f(keyPair) - } -} - -func NewGeneratedKeyRing(len int) *KeyRing { - keyRing := NewKeyRing() - for i := 0; i < len; i++ { - keyRing.AddKeyPair(GenerateNewKeyPair()) - } - return keyRing -} - -func NewKeyRingFromFile(secfile string) (*KeyRing, error) { - var content []byte - var err error - content, err = ioutil.ReadFile(secfile) - if err != nil { - return nil, err - } - keyRing, err := NewKeyRingFromString(string(content)) - if err != nil { - return nil, err - } - return keyRing, nil -} - -func NewKeyRingFromString(content string) (*KeyRing, error) { - secretStrings := strings.Split(content, "\n") - var secrets [][]byte - for _, secretString := range secretStrings { - secret := secretString - words := strings.Split(secretString, " ") - if len(words) == 24 { - secret = MnemonicDecode(words) - } else if len(words) != 1 { - return nil, fmt.Errorf("Unrecognised key format") - } - - if len(secret) != 0 { - secrets = append(secrets, ethutil.Hex2Bytes(secret)) - } - } - - return NewKeyRingFromSecrets(secrets) -} - -func NewKeyRingFromSecrets(secs [][]byte) (*KeyRing, error) { - keyRing := NewKeyRing() - for _, sec := range secs { - keyPair, err := NewKeyPairFromSec(sec) - if err != nil { - return nil, err - } - keyRing.AddKeyPair(keyPair) - } - return keyRing, nil -} - -func NewKeyRingFromBytes(data []byte) (*KeyRing, error) { - var secrets [][]byte - it := ethutil.NewValueFromBytes(data).NewIterator() - for it.Next() { - secret := it.Value().Bytes() - secrets = append(secrets, secret) - } - keyRing, err := NewKeyRingFromSecrets(secrets) - if err != nil { - return nil, err - } - return keyRing, nil -} - -func (k *KeyRing) RlpEncode() []byte { - return k.RlpValue().Encode() -} - -func (k *KeyRing) RlpValue() *ethutil.Value { - v := ethutil.EmptyValue() - k.Each(func(keyPair *KeyPair) { - v.Append(keyPair.RlpValue()) - }) - return v -} diff --git a/crypto/keys_test.go b/crypto/keys_test.go deleted file mode 100644 index 56e851969..000000000 --- a/crypto/keys_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package crypto - -// import ( -// "github.com/ethereum/go-ethereum/ethdb" -// // "io/ioutil" -// "fmt" -// "os" -// "path" -// "testing" -// ) - -// // test if persistence layer works -// func TestDBKeyManager(t *testing.T) { -// memdb, _ := ethdb.NewMemDatabase() -// keyManager0 := NewDBKeyManager(memdb) -// err := keyManager0.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// keyManager1 := NewDBKeyManager(memdb) -// err = keyManager1.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// if string(keyManager0.PrivateKey()) != string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be identical via db persistence", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } -// err = keyManager1.Init("", 0, true) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// if string(keyManager0.PrivateKey()) == string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be be different despite db persistence if force generate", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } -// } - -// func TestFileKeyManager(t *testing.T) { -// basedir0 := "/tmp/ethtest0" -// os.RemoveAll(basedir0) -// os.Mkdir(basedir0, 0777) - -// keyManager0 := NewFileKeyManager(basedir0) -// err := keyManager0.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } - -// keyManager1 := NewFileKeyManager(basedir0) - -// err = keyManager1.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// if string(keyManager0.PrivateKey()) != string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be identical via db persistence", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } - -// err = keyManager1.Init("", 0, true) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// if string(keyManager0.PrivateKey()) == string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be be different despite db persistence if force generate", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } -// } - -// // cursor errors -// func TestCursorErrors(t *testing.T) { -// memdb, _ := ethdb.NewMemDatabase() -// keyManager0 := NewDBKeyManager(memdb) -// err := keyManager0.Init("", 0, false) -// err = keyManager0.Init("", 1, false) -// if err == nil { -// t.Error("Expected cursor error") -// } -// err = keyManager0.SetCursor(1) -// if err == nil { -// t.Error("Expected cursor error") -// } -// } - -// func TestExportImport(t *testing.T) { -// memdb, _ := ethdb.NewMemDatabase() -// keyManager0 := NewDBKeyManager(memdb) -// err := keyManager0.Init("", 0, false) -// basedir0 := "/tmp/ethtest0" -// os.RemoveAll(basedir0) -// os.Mkdir(basedir0, 0777) -// keyManager0.Export(basedir0) - -// keyManager1 := NewFileKeyManager(basedir0) -// err = keyManager1.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// fmt.Printf("keyRing: %v\n", keyManager0.KeyPair()) -// fmt.Printf("keyRing: %v\n", keyManager1.KeyPair()) -// if string(keyManager0.PrivateKey()) != string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be identical via export to filestore basedir", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } -// path.Join("") - -// // memdb, _ = ethdb.NewMemDatabase() -// // keyManager2 := NewDBKeyManager(memdb) -// // err = keyManager2.InitFromSecretsFile("", 0, path.Join(basedir0, "default.prv")) -// // if err != nil { -// // t.Error("Unexpected error: ", err) -// // } -// // if string(keyManager0.PrivateKey()) != string(keyManager2.PrivateKey()) { -// // t.Error("Expected private keys %s, %s, to be identical via export/import prv", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// // } - -// // memdb, _ = ethdb.NewMemDatabase() -// // keyManager3 := NewDBKeyManager(memdb) -// // err = keyManager3.InitFromSecretsFile("", 0, path.Join(basedir0, "default.mne")) -// // if err != nil { -// // t.Error("Unexpected error: ", err) -// // } -// // if string(keyManager0.PrivateKey()) != string(keyManager3.PrivateKey()) { -// // t.Error("Expected private keys %s, %s, to be identical via export/import mnemonic file", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// // } -// } From ae38871a54b1ccb51346ce5d99beb863b7febe66 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:33:59 +0100 Subject: [PATCH 25/37] cmd/ethereum: remove "prompter" in identifiers --- cmd/ethereum/js.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index 96e292733..de73e83a2 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -41,14 +41,14 @@ type prompter interface { PasswordPrompt(p string) (string, error) } -type dumbPrompter struct{ r *bufio.Reader } +type dumbterm struct{ r *bufio.Reader } -func (r dumbPrompter) Prompt(p string) (string, error) { +func (r dumbterm) Prompt(p string) (string, error) { fmt.Print(p) return r.r.ReadString('\n') } -func (r dumbPrompter) PasswordPrompt(p string) (string, error) { +func (r dumbterm) PasswordPrompt(p string) (string, error) { fmt.Println("!! Unsupported terminal, password will echo.") fmt.Print(p) input, err := bufio.NewReader(os.Stdin).ReadString('\n') @@ -56,13 +56,14 @@ func (r dumbPrompter) PasswordPrompt(p string) (string, error) { return input, err } -func (r dumbPrompter) AppendHistory(string) {} +func (r dumbterm) AppendHistory(string) {} type jsre struct { re *javascript.JSRE ethereum *eth.Ethereum xeth *xeth.XEth ps1 string + prompter } @@ -73,7 +74,7 @@ func newJSRE(ethereum *eth.Ethereum) *jsre { js.initStdFuncs() if !liner.TerminalSupported() { - js.prompter = dumbPrompter{bufio.NewReader(os.Stdin)} + js.prompter = dumbterm{bufio.NewReader(os.Stdin)} } else { lr := liner.NewLiner() lr.SetCtrlCAborts(true) @@ -87,13 +88,13 @@ func newJSRE(ethereum *eth.Ethereum) *jsre { func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool { p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx) - answer, _ := self.prompter.Prompt(p) + answer, _ := self.Prompt(p) return strings.HasPrefix(strings.Trim(answer, " "), "y") } func (self *jsre) UnlockAccount(addr []byte) bool { fmt.Printf("Please unlock account %x.\n", addr) - pass, err := self.prompter.PasswordPrompt("Passphrase: ") + pass, err := self.PasswordPrompt("Passphrase: ") if err != nil { return false } @@ -124,7 +125,7 @@ func (self *jsre) exec(filename string) error { func (self *jsre) interactive() { for { - input, err := self.prompter.Prompt(self.ps1) + input, err := self.Prompt(self.ps1) if err != nil { return } @@ -138,7 +139,7 @@ func (self *jsre) interactive() { return } hist := str[:len(str)-1] - self.prompter.AppendHistory(hist) + self.AppendHistory(hist) self.parseInput(str) str = "" } From 9b3ae1fdb7903bc390fcb5c34edddace2a0e1dd8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:34:52 +0100 Subject: [PATCH 26/37] eth: fix tests --- eth/backend.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 8417b572b..ac8a016fe 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -25,7 +25,7 @@ import ( ) var ( - ethlogger = logger.NewLogger("SERV") + servlogger = logger.NewLogger("SERV") jsonlogger = logger.NewJsonLogger() defaultBootNodes = []*discover.Node{ @@ -74,7 +74,7 @@ func (cfg *Config) parseBootNodes() []*discover.Node { } n, err := discover.ParseNode(url) if err != nil { - ethlogger.Errorf("Bootstrap URL %s: %v\n", url, err) + servlogger.Errorf("Bootstrap URL %s: %v\n", url, err) continue } ns = append(ns, n) @@ -98,7 +98,7 @@ func (cfg *Config) nodeKey() (*ecdsa.PrivateKey, error) { return nil, fmt.Errorf("could not generate server key: %v", err) } if err := ioutil.WriteFile(keyfile, crypto.FromECDSA(key), 0600); err != nil { - ethlogger.Errorln("could not persist nodekey: ", err) + servlogger.Errorln("could not persist nodekey: ", err) } return key, nil } @@ -134,7 +134,7 @@ type Ethereum struct { func New(config *Config) (*Ethereum, error) { // Boostrap database - ethlogger := logger.New(config.DataDir, config.LogFile, config.LogLevel, config.LogFormat) + servlogger := logger.New(config.DataDir, config.LogFile, config.LogLevel, config.LogFormat) blockDb, err := ethdb.NewLDBDatabase(path.Join(config.DataDir, "blockchain")) if err != nil { @@ -161,7 +161,7 @@ func New(config *Config) (*Ethereum, error) { blockDb: blockDb, stateDb: stateDb, eventMux: &event.TypeMux{}, - logger: ethlogger, + logger: servlogger, accountManager: config.AccountManager, DataDir: config.DataDir, } @@ -256,7 +256,7 @@ func (s *Ethereum) Start() error { s.blockSub = s.eventMux.Subscribe(core.NewMinedBlockEvent{}) go s.blockBroadcastLoop() - ethlogger.Infoln("Server started") + servlogger.Infoln("Server started") return nil } @@ -284,7 +284,7 @@ func (s *Ethereum) Stop() { s.whisper.Stop() } - ethlogger.Infoln("Server stopped") + servlogger.Infoln("Server stopped") close(s.shutdownChan) } From e6118f2d7914fdc52c6ad86e48561f3bc1adaf61 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:35:21 +0100 Subject: [PATCH 27/37] state: fix tests --- state/state_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/state/state_test.go b/state/state_test.go index ee1cf9286..6b6d52881 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -33,16 +33,16 @@ func (s *StateSuite) TestDump(c *checker.C) { // check that dump contains the state objects that are in trie got := string(s.state.Dump()) want := `{ - "root": "4e3a59299745ba6752247c8b91d0f716dac9ec235861c91f5ac1894a361d87ba", + "root": "6e277ae8357d013e50f74eedb66a991f6922f93ae03714de58b3d0c5e9eee53f", "accounts": { - "0000000000000000000000000000000000000001": { + "1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d": { "balance": "22", "nonce": 0, "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "storage": {} }, - "0000000000000000000000000000000000000102": { + "a17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1": { "balance": "0", "nonce": 0, "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", From 80985f97da8174576ee227909035a364af2fd6c9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:35:46 +0100 Subject: [PATCH 28/37] cmd/evm, core, ethdb, state, tests/helper: remove ReadConfig calls --- cmd/evm/main.go | 2 -- core/chain_manager_test.go | 2 -- core/helper_test.go | 2 -- ethdb/database_test.go | 2 -- state/state_test.go | 1 - tests/helper/init.go | 3 --- 6 files changed, 12 deletions(-) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index d6a93460e..960558bb4 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -59,8 +59,6 @@ func main() { logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(*loglevel))) - ethutil.ReadConfig("/tmp/evmtest", "/tmp/evm", "") - db, _ := ethdb.NewMemDatabase() statedb := state.New(nil, db) sender := statedb.NewStateObject([]byte("sender")) diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go index e78c2e980..91822f9e2 100644 --- a/core/chain_manager_test.go +++ b/core/chain_manager_test.go @@ -12,14 +12,12 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rlp" ) func init() { runtime.GOMAXPROCS(runtime.NumCPU()) - ethutil.ReadConfig("/tmp/ethtest", "/tmp/ethtest", "ETH") } // Test fork of length N starting from block i diff --git a/core/helper_test.go b/core/helper_test.go index 473576e3f..36d9e70a8 100644 --- a/core/helper_test.go +++ b/core/helper_test.go @@ -62,8 +62,6 @@ func (tm *TestManager) Db() ethutil.Database { } func NewTestManager() *TestManager { - ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "ETH") - db, err := ethdb.NewMemDatabase() if err != nil { fmt.Println("Could not create mem-db, failing") diff --git a/ethdb/database_test.go b/ethdb/database_test.go index 2cbaf58e0..7de30fd81 100644 --- a/ethdb/database_test.go +++ b/ethdb/database_test.go @@ -7,8 +7,6 @@ import ( ) func TestCompression(t *testing.T) { - ethutil.ReadConfig("", "/tmp", "") - db, err := NewLDBDatabase("testdb") if err != nil { t.Fatal(err) diff --git a/state/state_test.go b/state/state_test.go index 6b6d52881..07e35f7e2 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -57,7 +57,6 @@ func (s *StateSuite) TestDump(c *checker.C) { } func (s *StateSuite) SetUpTest(c *checker.C) { - ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "") db, _ := ethdb.NewMemDatabase() s.state = New(nil, db) } diff --git a/tests/helper/init.go b/tests/helper/init.go index df98b9e42..e0892d8f6 100644 --- a/tests/helper/init.go +++ b/tests/helper/init.go @@ -4,7 +4,6 @@ import ( "log" "os" - "github.com/ethereum/go-ethereum/ethutil" logpkg "github.com/ethereum/go-ethereum/logger" ) @@ -14,6 +13,4 @@ var Log = logpkg.NewLogger("TEST") func init() { Logger = logpkg.NewStdLogSystem(os.Stdout, log.LstdFlags, logpkg.InfoLevel) logpkg.AddLogSystem(Logger) - - ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "") } From 0395c174ca5221dd936c19143ced454dac56d54a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 14:31:54 +0100 Subject: [PATCH 29/37] cmd/ethereum: fix line editing setup and history --- cmd/ethereum/js.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index de73e83a2..5432fb9b1 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -63,6 +63,7 @@ type jsre struct { ethereum *eth.Ethereum xeth *xeth.XEth ps1 string + atexit func() prompter } @@ -77,11 +78,13 @@ func newJSRE(ethereum *eth.Ethereum) *jsre { js.prompter = dumbterm{bufio.NewReader(os.Stdin)} } else { lr := liner.NewLiner() - lr.SetCtrlCAborts(true) - defer lr.Close() js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) }) - defer js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) + lr.SetCtrlCAborts(true) js.prompter = lr + js.atexit = func() { + js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) + lr.Close() + } } return js } @@ -100,7 +103,6 @@ func (self *jsre) UnlockAccount(addr []byte) bool { } // TODO: allow retry if err := self.ethereum.AccountManager().Unlock(addr, pass); err != nil { - fmt.Println("Unlocking failed: ", err) return false } else { fmt.Println("Account is now unlocked for this session.") @@ -127,7 +129,7 @@ func (self *jsre) interactive() { for { input, err := self.Prompt(self.ps1) if err != nil { - return + break } if input == "" { continue @@ -136,7 +138,7 @@ func (self *jsre) interactive() { self.setIndent() if indentCount <= 0 { if input == "exit" { - return + break } hist := str[:len(str)-1] self.AppendHistory(hist) @@ -144,6 +146,9 @@ func (self *jsre) interactive() { str = "" } } + if self.atexit != nil { + self.atexit() + } } func (self *jsre) withHistory(op func(*os.File)) { From 0f67f1e9de856404136dcdce78fc0ab93bab6a84 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 14:55:00 +0100 Subject: [PATCH 30/37] xeth: fix signing transaction after unlock --- xeth/xeth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xeth/xeth.go b/xeth/xeth.go index f8b537321..60262bf17 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -360,7 +360,7 @@ func (self *XEth) sign(tx *types.Transaction, from []byte, didUnlock bool) error return fmt.Errorf("could not unlock sender account") } // retry signing, the account should now be unlocked. - self.sign(tx, from, true) + return self.sign(tx, from, true) } else if err != nil { return err } From 4ba7871374fd3af08bfad972509267842db6df99 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 15:41:08 +0100 Subject: [PATCH 31/37] accounts: return ErrNoKeys if key directory does not exist --- accounts/account_manager.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index fdd7d83e9..646dc8376 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -36,6 +36,7 @@ import ( "bytes" "crypto/ecdsa" crand "crypto/rand" + "os" "errors" "sync" @@ -89,7 +90,9 @@ func (am *Manager) Coinbase() (addr []byte, err error) { func (am *Manager) firstAddr() ([]byte, error) { addrs, err := am.keyStore.GetKeyAddresses() - if err != nil { + if os.IsNotExist(err) { + return nil, ErrNoKeys + } else if err != nil { return nil, err } if len(addrs) == 0 { @@ -147,7 +150,9 @@ func (am *Manager) NewAccount(auth string) (Account, error) { func (am *Manager) Accounts() ([]Account, error) { addresses, err := am.keyStore.GetKeyAddresses() - if err != nil { + if os.IsNotExist(err) { + return nil, ErrNoKeys + } else if err != nil { return nil, err } accounts := make([]Account, len(addresses)) From c3f94a4341056d36ec9f42dd80702ec7ec6986af Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 15:42:25 +0100 Subject: [PATCH 32/37] cmd/utils: remove extra space in fatal error message --- cmd/utils/cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index a802a08da..c5568948d 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -98,7 +98,7 @@ func initDataDir(Datadir string) { func exit(err error) { status := 0 if err != nil { - fmt.Fprintln(os.Stderr, "Fatal: ", err) + fmt.Fprintln(os.Stderr, "Fatal:", err) status = 1 } logger.Flush() From 9d4e1e8f8bbdfa84937bcdcdc6b4ca4ba6cd79a2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 15:42:58 +0100 Subject: [PATCH 33/37] eth: return account errors directly --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index ac8a016fe..9d7ce4988 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -168,7 +168,7 @@ func New(config *Config) (*Ethereum, error) { cb, err := eth.accountManager.Coinbase() if err != nil { - return nil, fmt.Errorf("no coinbase: %v", err) + return nil, err } eth.chainManager = core.NewChainManager(blockDb, stateDb, eth.EventMux()) From 0bb7377ebee69c3467c21d355dd24945d0becad5 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 15:44:05 +0100 Subject: [PATCH 34/37] cmd/ethereum: show more helpful message if no accounts exist --- cmd/ethereum/main.go | 19 +++++++++++++++++-- cmd/mist/main.go | 5 ++++- cmd/utils/flags.go | 8 ++------ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 1703c02bb..8beba471a 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -29,6 +29,7 @@ import ( "time" "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" @@ -148,14 +149,28 @@ func main() { func run(ctx *cli.Context) { fmt.Printf("Welcome to the FRONTIER\n") utils.HandleInterrupt() - eth := utils.GetEthereum(ClientIdentifier, Version, ctx) + eth, err := utils.GetEthereum(ClientIdentifier, Version, ctx) + if err == accounts.ErrNoKeys { + utils.Fatalf(`No accounts configured. +Please run 'ethereum account new' to create a new account.`) + } else if err != nil { + utils.Fatalf("%v", err) + } + startEth(ctx, eth) // this blocks the thread eth.WaitForShutdown() } func runjs(ctx *cli.Context) { - eth := utils.GetEthereum(ClientIdentifier, Version, ctx) + eth, err := utils.GetEthereum(ClientIdentifier, Version, ctx) + if err == accounts.ErrNoKeys { + utils.Fatalf(`No accounts configured. +Please run 'ethereum account new' to create a new account.`) + } else if err != nil { + utils.Fatalf("%v", err) + } + startEth(ctx, eth) repl := newJSRE(eth) if len(ctx.Args()) == 0 { diff --git a/cmd/mist/main.go b/cmd/mist/main.go index c27f1dba9..9a773e33a 100644 --- a/cmd/mist/main.go +++ b/cmd/mist/main.go @@ -95,7 +95,10 @@ func run(ctx *cli.Context) { tstart := time.Now() // TODO: show qml popup instead of exiting if initialization fails. - ethereum := utils.GetEthereum(ClientIdentifier, Version, ctx) + ethereum, err := utils.GetEthereum(ClientIdentifier, Version, ctx) + if err != nil { + utils.Fatalf("%v", err) + } utils.StartRPC(ethereum, ctx) go utils.StartEthereum(ethereum) fmt.Println("initializing eth stack took", time.Since(tstart)) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cde5fa024..97d312dd4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -157,8 +157,8 @@ func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) { return key } -func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum { - ethereum, err := eth.New(ð.Config{ +func GetEthereum(clientID, version string, ctx *cli.Context) (*eth.Ethereum, error) { + return eth.New(ð.Config{ Name: p2p.MakeName(clientID, version), DataDir: ctx.GlobalString(DataDirFlag.Name), LogFile: ctx.GlobalString(LogFileFlag.Name), @@ -175,10 +175,6 @@ func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum { Dial: true, BootNodes: ctx.GlobalString(BootnodesFlag.Name), }) - if err != nil { - exit(err) - } - return ethereum } func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.Database) { From 2407f006adbcbe5f5405b8591c5e9845cfa7dd5c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 16:41:33 +0100 Subject: [PATCH 35/37] Godeps: bump github.com/codegangsta/cli --- Godeps/Godeps.json | 4 +-- .../src/github.com/codegangsta/cli/app.go | 31 ++++++++++++++++--- .../github.com/codegangsta/cli/app_test.go | 3 ++ .../src/github.com/codegangsta/cli/command.go | 2 +- .../src/github.com/codegangsta/cli/help.go | 13 +++++--- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 8118b4509..e0a932f8b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -17,8 +17,8 @@ }, { "ImportPath": "github.com/codegangsta/cli", - "Comment": "1.2.0-74-g50c77ec", - "Rev": "50c77ecec0068c9aef9d90ae0fd0fdf410041da3" + "Comment": "1.2.0-81-g3e09053", + "Rev": "3e0905345cd2c5366530dbcdce62457f2ce16e7c" }, { "ImportPath": "github.com/ethereum/ethash", diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/app.go b/Godeps/_workspace/src/github.com/codegangsta/cli/app.go index 928983ebd..3e7d5a63c 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/app.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/app.go @@ -43,9 +43,11 @@ type App struct { CommandNotFound func(context *Context, command string) // Compilation date Compiled time.Time - // Author + // List of all authors who contributed + Authors []Author + // Name of Author (Note: Use App.Authors, this is deprecated) Author string - // Author e-mail + // Email of Author (Note: Use App.Authors, this is deprecated) Email string // Writer writer to write output to Writer io.Writer @@ -70,14 +72,19 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), - Author: "Author", - Email: "unknown@email", + Author: "Dr. James", + Email: "who@gmail.com", + Authors: []Author{{"Jim", "jim@corporate.com"}, {"Hank", "hank@indiepalace.com"}}, Writer: os.Stdout, } } // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination func (a *App) Run(arguments []string) (err error) { + if a.Author != "" && a.Author != "" { + a.Authors = append(a.Authors, Author{a.Author, a.Email}) + } + if HelpPrinter == nil { defer func() { HelpPrinter = nil @@ -294,3 +301,19 @@ func (a *App) appendFlag(flag Flag) { a.Flags = append(a.Flags, flag) } } + +// Author represents someone who has contributed to a cli project. +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + +// String makes Author comply to the Stringer interface, to allow an easy print in the templating process +func (a Author) String() string { + e := "" + if a.Email != "" { + e = "<" + a.Email + "> " + } + + return fmt.Sprintf("%v %v", a.Name, e) +} diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go index fd2b0e826..6143d364b 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go @@ -21,6 +21,9 @@ func ExampleApp() { app.Action = func(c *cli.Context) { fmt.Printf("Hello %v\n", c.String("name")) } + app.Author = "Harrison" + app.Email = "harrison@lolwut.com" + app.Authors = []cli.Author{{"Oliver Allen", "oliver@toyshop.com"}} app.Run(os.Args) // Output: // Hello Jeremy diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/command.go b/Godeps/_workspace/src/github.com/codegangsta/cli/command.go index 5747e52e8..07c919a87 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/command.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/command.go @@ -119,7 +119,7 @@ func (c Command) Run(ctx *Context) error { // Returns true if Command.Name or Command.ShortName matches given name func (c Command) HasName(name string) bool { - return c.Name == name || c.ShortName == name + return c.Name == name || (c.ShortName != "" && c.ShortName == name) } func (c Command) startApp(ctx *Context) error { diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/help.go b/Godeps/_workspace/src/github.com/codegangsta/cli/help.go index bfb278851..8d176556a 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/help.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/help.go @@ -12,11 +12,10 @@ USAGE: {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] VERSION: - {{.Version}}{{if or .Author .Email}} + {{.Version}} -AUTHOR:{{if .Author}} - {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} - {{.Email}}{{end}}{{end}} +AUTHOR(S): + {{range .Authors}}{{ . }} {{end}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} @@ -112,6 +111,12 @@ func DefaultAppComplete(c *Context) { // Prints help for the given command func ShowCommandHelp(c *Context, command string) { + // show the subcommand help for a command with subcommands + if command == "" { + HelpPrinter(SubcommandHelpTemplate, c.App) + return + } + for _, c := range c.App.Commands { if c.HasName(command) { HelpPrinter(CommandHelpTemplate, c) From 221fae411278825dcaa0ec4058f065752046869b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 16:44:22 +0100 Subject: [PATCH 36/37] cmd/ethereum: show some help before prompting for encryption passphrase --- cmd/ethereum/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 8beba471a..ed17fcc1f 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -186,6 +186,7 @@ Please run 'ethereum account new' to create a new account.`) func startEth(ctx *cli.Context, eth *eth.Ethereum) { utils.StartEthereum(eth) + // Start auxiliary services if enabled. if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { utils.StartRPC(eth, ctx) } @@ -207,6 +208,8 @@ func accountList(ctx *cli.Context) { func accountCreate(ctx *cli.Context) { am := utils.GetAccountManager(ctx) + fmt.Println("The new account will be encrypted with a passphrase.") + fmt.Println("Please enter a passphrase now.") auth, err := readPassword("Passphrase: ", true) if err != nil { utils.Fatalf("%v", err) From 972e2c1e31067a9bab77228c19348b66964ce643 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 16:44:48 +0100 Subject: [PATCH 37/37] cmd/utils: improve CLI help templates Help for a specific command now shows available subcommands. --- cmd/utils/flags.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 97d312dd4..e945a32ee 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -24,11 +24,38 @@ import ( "github.com/ethereum/go-ethereum/xeth" ) +func init() { + cli.AppHelpTemplate = `{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] + +VERSION: + {{.Version}} + +COMMANDS: + {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{if .Flags}} +GLOBAL OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +` + + cli.CommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...] +{{if .Description}}{{.Description}} +{{end}}{{if .Subcommands}} +SUBCOMMANDS: + {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .Flags}} +OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +` +} + // NewApp creates an app with sane defaults. func NewApp(version, usage string) *cli.App { app := cli.NewApp() app.Name = path.Base(os.Args[0]) app.Author = "" + app.Authors = nil app.Email = "" app.Version = version app.Usage = usage