diff options
-rw-r--r-- | accounts/account_manager.go | 137 | ||||
-rw-r--r-- | accounts/accounts_test.go | 11 | ||||
-rw-r--r-- | cmd/blocktest/flags.go | 41 | ||||
-rw-r--r-- | cmd/blocktest/main.go | 186 | ||||
-rw-r--r-- | cmd/ethereum/main.go | 89 | ||||
-rw-r--r-- | cmd/mist/assets/qml/main.qml | 5 | ||||
-rw-r--r-- | cmd/mist/assets/qml/views/info.qml | 41 | ||||
-rw-r--r-- | cmd/mist/bindings.go | 33 | ||||
-rw-r--r-- | cmd/mist/debugger.go | 8 | ||||
-rw-r--r-- | cmd/mist/flags.go | 130 | ||||
-rw-r--r-- | cmd/mist/gui.go | 56 | ||||
-rw-r--r-- | cmd/mist/main.go | 101 | ||||
-rw-r--r-- | cmd/mist/ui_lib.go | 1 | ||||
-rw-r--r-- | cmd/utils/cmd.go | 52 | ||||
-rw-r--r-- | cmd/utils/flags.go | 79 | ||||
-rw-r--r-- | core/manager.go | 2 | ||||
-rw-r--r-- | core/state_transition.go | 4 | ||||
-rw-r--r-- | core/types/transaction.go | 8 | ||||
-rw-r--r-- | eth/backend.go | 55 | ||||
-rw-r--r-- | javascript/javascript_runtime.go | 1 | ||||
-rw-r--r-- | javascript/types.go | 4 | ||||
-rw-r--r-- | rpc/api.go | 37 | ||||
-rw-r--r-- | rpc/args.go | 1 | ||||
-rw-r--r-- | rpc/http.go | 52 | ||||
-rw-r--r-- | rpc/http/server.go | 124 | ||||
-rw-r--r-- | xeth/xeth.go | 97 |
26 files changed, 492 insertions, 863 deletions
diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 3e9fa7799..4575334bf 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -33,6 +33,8 @@ and accounts persistence is derived from stored keys' addresses package accounts import ( + "bytes" + "crypto/ecdsa" crand "crypto/rand" "errors" @@ -42,77 +44,102 @@ 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 { Address []byte } -type AccountManager struct { - keyStore crypto.KeyStore2 - unlockedKeys map[string]crypto.Key - unlockMilliseconds time.Duration - mutex sync.RWMutex +type Manager struct { + keyStore crypto.KeyStore2 + unlocked map[string]*unlocked + unlockTime time.Duration + mutex sync.RWMutex +} + +type unlocked struct { + *crypto.Key + abort chan struct{} +} + +func NewManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *Manager { + return &Manager{ + keyStore: keyStore, + unlocked: make(map[string]*unlocked), + unlockTime: unlockTime, + } } -func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) AccountManager { - keysMap := make(map[string]crypto.Key) - am := &AccountManager{ - keyStore: keyStore, - unlockedKeys: keysMap, - unlockMilliseconds: unlockMilliseconds, +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() +} + +func (am *Manager) firstAddr() ([]byte, error) { + addrs, err := am.keyStore.GetKeyAddresses() + if err != nil { + return nil, err + } + if len(addrs) == 0 { + return nil, ErrNoKeys } - return *am + 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(fromAccount *Account, toSign []byte) (signature []byte, err error) { +func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) { am.mutex.RLock() - unlockedKey := am.unlockedKeys[string(fromAccount.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) 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 *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 } - am.mutex.RLock() - am.unlockedKeys[string(fromAccount.Address)] = *key - am.mutex.RUnlock() - go unlockLater(am, fromAccount.Address) + u := am.addUnlocked(a.Address, key) + go am.dropLater(a.Address, u) signature, err = crypto.Sign(toSign, key.PrivateKey) 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 nil, err - } - ua := &Account{ - Address: key.Address, + return Account{}, err } - return ua, err + 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 } - accounts := make([]Account, len(addresses)) - for i, addr := range addresses { accounts[i] = Account{ Address: addr, @@ -121,12 +148,44 @@ func (am *AccountManager) Accounts() ([]Account, error) { return accounts, err } -func unlockLater(am *AccountManager, addr []byte) { +func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { + u := &unlocked{Key: key, abort: make(chan struct{})} + am.mutex.Lock() + 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 *Manager) dropLater(addr []byte, u *unlocked) { + t := time.NewTimer(am.unlockTime) + defer t.Stop() select { - case <-time.After(time.Millisecond * am.unlockMilliseconds): + case <-u.abort: + // just quit + case <-t.C: + am.mutex.Lock() + // 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(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 } - am.mutex.RLock() - // TODO: how do we know the key is actually gone from memory? - delete(am.unlockedKeys, string(addr)) - am.mutex.RUnlock() } diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 44d1d72f1..b90da2892 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -3,15 +3,16 @@ 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) { ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 100) + am := NewManager(ks, 100*time.Millisecond) pass := "" // not used but required by API a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -21,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 { @@ -37,7 +38,7 @@ func TestAccountManager(t *testing.T) { func TestAccountManagerLocking(t *testing.T) { ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 200) + am := NewManager(ks, 200*time.Millisecond) pass := "foo" a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -61,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 { 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 <http://www.gnu.org/licenses/>. -*/ -/** - * @authors - * Gustav Simonsson <gustav.simonsson@gmail.com> - */ -package main - -import ( - "flag" - "fmt" - "os" -) - -var ( - TestFile string -) - -func Init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s <testfile>\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..d9d97abc9 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,44 @@ 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() + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "%s <testfile>\n", os.Args[0]) + flag.PrintDefaults() + } + flag.Parse() - Init() + runtime.GOMAXPROCS(runtime.NumCPU()) + logger.AddLogSystem(logger.NewStdLogSystem(os.Stderr, log.LstdFlags, logger.DebugDetailLevel)) + defer func() { logger.Flush() }() - if len(TestFile) < 1 { - log.Fatal("Please specify test file") + if len(os.Args) < 2 { + utils.Fatalf("Please specify a test file as the first argument.") } - blocks, err := loadBlocksFromTestFile(TestFile) + blocks, err := loadBlocksFromTestFile(os.Args[1]) if err != nil { - panic(err) + utils.Fatalf("Could not load blocks: %v", err) } - runtime.GOMAXPROCS(runtime.NumCPU()) + 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") + } +} - defer func() { - logger.Flush() - }() - - utils.HandleInterrupt() - - 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 { + blockdb, err := ethdb.NewMemDatabase() + if err != nil { + utils.Fatalf("Could not create in-memory database: %v", err) + } + statedb, err := ethdb.NewMemDatabase() + if err != nil { + utils.Fatalf("Could not create in-memory database: %v", err) + } + return core.NewChainManager(blockdb, statedb, new(event.TypeMux)) } func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { @@ -207,9 +141,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 +205,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")) } diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 8b361f7ae..1ae8e46a2 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 ( @@ -43,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{ @@ -61,6 +61,23 @@ 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", Usage: `dump a specific block from storage`, @@ -88,13 +105,9 @@ runtime will execute the file and exit. Usage: `import a blockchain file`, }, } - app.Author = "" - app.Email = "" app.Flags = []cli.Flag{ utils.BootnodesFlag, utils.DataDirFlag, - utils.KeyRingFlag, - utils.KeyStoreFlag, utils.ListenPortFlag, utils.LogFileFlag, utils.LogFormatFlag, @@ -158,15 +171,44 @@ 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() } } +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.") @@ -202,12 +244,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 @@ -217,3 +253,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/mist/assets/qml/main.qml b/cmd/mist/assets/qml/main.qml index 6824d2ba9..1626a63a9 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/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 c63f11591..b56c0dddf 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,19 +36,7 @@ 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(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,18 +48,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) -} - -// 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 + return gui.xeth.Transact(from, recipient, value, gas, gasPrice, data) } func (self *Gui) AddPlugin(pluginPath string) { @@ -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 <http://www.gnu.org/licenses/>. -*/ -/** - * @authors - * Jeffrey Wilcke <i@jev.io> - */ -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:<IP>)") - ) - 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 53ca35574..a49e9f6f8 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,7 +160,11 @@ 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 { + // TODO: UI feedback? + } } func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) { @@ -191,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) } } @@ -259,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" @@ -480,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 7d78c9c02..c27f1dba9 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.9.0" ) -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 + }) +} diff --git a/cmd/mist/ui_lib.go b/cmd/mist/ui_lib.go index 187d5b2d6..32e8ce5af 100644 --- a/cmd/mist/ui_lib.go +++ b/cmd/mist/ui_lib.go @@ -153,6 +153,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/cmd/utils/cmd.go b/cmd/utils/cmd.go index a77c6ad4d..a802a08da 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -29,14 +29,11 @@ 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" "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") @@ -98,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 { @@ -134,47 +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 StartRpc(ethereum *eth.Ethereum, RpcListenAddress string, RpcPort int) { - var err error - ethereum.RpcServer, err = rpchttp.NewRpcHttpServer(xeth.New(ethereum, nil), 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 88ff3558d..ee7ea4c79 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2,10 +2,16 @@ package utils import ( "crypto/ecdsa" + "fmt" + "net" + "net/http" + "os" "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" @@ -15,8 +21,21 @@ 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. +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. @@ -36,16 +55,6 @@ var ( Name: "vmdebug", Usage: "Virtual Machine debug output", } - 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", @@ -151,23 +160,21 @@ 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), - VmDebug: ctx.GlobalBool(VMDebugFlag.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), + VmDebug: ctx.GlobalBool(VMDebugFlag.Name), + 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) @@ -188,3 +195,21 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.D } return core.NewChainManager(blockDb, stateDb, new(event.TypeMux)), blockDb, stateDb } + +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) +} + +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, nil), dataDir)) +} diff --git a/core/manager.go b/core/manager.go index 803069377..c4052cc05 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 BlockDb() ethutil.Database StateDb() ethutil.Database EventMux() *event.TypeMux diff --git a/core/state_transition.go b/core/state_transition.go index f54acd6ee..9b67de149 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -45,8 +45,6 @@ type StateTransition struct { } type Message interface { - Hash() []byte - From() []byte To() []byte @@ -153,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 { 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 584d60c7e..8417b572b 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" @@ -19,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/vm" "github.com/ethereum/go-ethereum/whisper" ) @@ -38,11 +38,9 @@ var ( type Config struct { Name string - KeyStore string DataDir string LogFile string LogLevel int - KeyRing string LogFormat string VmDebug bool @@ -61,9 +59,8 @@ type Config struct { Shh bool Dial bool - MinerThreads int - - KeyManager *crypto.KeyManager + MinerThreads int + AccountManager *accounts.Manager } func (cfg *Config) parseBootNodes() []*discover.Node { @@ -120,6 +117,7 @@ type Ethereum struct { txPool *core.TxPool chainManager *core.ChainManager blockPool *blockpool.BlockPool + accountManager *accounts.Manager whisper *whisper.Whisper net *p2p.Server @@ -128,9 +126,6 @@ type Ethereum struct { blockSub event.Subscription miner *miner.Miner - RpcServer rpc.RpcServer - keyManager *crypto.KeyManager - logger logger.LogSystem Mining bool @@ -158,40 +153,31 @@ 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(blockDb) - 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(blockDb) //ethutil.Config.Db = db eth := &Ethereum{ - shutdownChan: make(chan bool), - blockDb: blockDb, - stateDb: stateDb, - keyManager: keyManager, - eventMux: &event.TypeMux{}, - logger: ethlogger, - DataDir: config.DataDir, + shutdownChan: make(chan bool), + blockDb: blockDb, + stateDb: stateDb, + eventMux: &event.TypeMux{}, + logger: ethlogger, + accountManager: config.AccountManager, + DataDir: config.DataDir, + } + + cb, err := eth.accountManager.Coinbase() + if err != nil { + return nil, fmt.Errorf("no coinbase: %v", err) } eth.chainManager = core.NewChainManager(blockDb, stateDb, eth.EventMux()) pow := ethash.New(eth.chainManager) - eth.txPool = core.NewTxPool(eth.EventMux()) eth.blockProcessor = core.NewBlockProcessor(stateDb, 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 @@ -225,9 +211,9 @@ 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.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 } @@ -241,7 +227,6 @@ func (s *Ethereum) IsListening() bool { return true } // Alwa 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 { @@ -292,10 +277,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() 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/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 70a8cf9b4..d6854bbab 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -10,6 +10,7 @@ package rpc import ( "math/big" + "path" "strings" "sync" "time" @@ -53,8 +54,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(), @@ -250,44 +251,24 @@ 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 } 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/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/rpc/http.go b/rpc/http.go new file mode 100644 index 000000000..857cf3221 --- /dev/null +++ b/rpc/http.go @@ -0,0 +1,52 @@ +package rpc + +import ( + "net/http" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/xeth" +) + +var rpchttplogger = logger.NewLogger("RPC-HTTP") + +const ( + jsonrpcver = "2.0" + maxSizeReqLength = 1024 * 1024 // 1MB +) + +// JSONRPC returns a handler that implements the Ethereum JSON-RPC API. +func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { + var json JsonWrapper + 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") + + if req.ContentLength > maxSizeReqLength { + jsonerr := &RpcErrorObject{-32700, "Error: Request too large"} + json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + return + } + + 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}) + }) +} diff --git a/rpc/http/server.go b/rpc/http/server.go deleted file mode 100644 index d8f0157f1..000000000 --- a/rpc/http/server.go +++ /dev/null @@ -1,124 +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 <http://www.gnu.org/licenses/>. -*/ -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 - -const maxSizeReqLength = 1024 * 1024 // 1MB - -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") - - if req.ContentLength > maxSizeReqLength { - jsonerr := &rpc.RpcErrorObject{-32700, "Error: Request too large"} - JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) - return - } - - 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) -} diff --git a/xeth/xeth.go b/xeth/xeth.go index 88ae253cd..a0491506b 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -7,8 +7,9 @@ package xeth import ( "bytes" "encoding/json" - "fmt" + "math/big" + "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" @@ -28,11 +29,11 @@ var pipelogger = logger.NewLogger("XETH") type Backend interface { BlockProcessor() *core.BlockProcessor ChainManager() *core.ChainManager + AccountManager() *accounts.Manager TxPool() *core.TxPool PeerCount() int IsListening() bool Peers() []*p2p.Peer - KeyManager() *crypto.KeyManager BlockDb() ethutil.Database StateDb() ethutil.Database EventMux() *event.TypeMux @@ -44,6 +45,7 @@ type XEth struct { eth Backend blockProcessor *core.BlockProcessor chainManager *core.ChainManager + accountManager *accounts.Manager state *State whisper *Whisper miner *miner.Miner @@ -61,6 +63,7 @@ func New(eth Backend, frontend ui.Interface) *XEth { eth: eth, blockProcessor: eth.BlockProcessor(), chainManager: eth.ChainManager(), + accountManager: eth.AccountManager(), whisper: NewWhisper(eth.Whisper()), miner: eth.Miner(), } @@ -120,7 +123,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 { @@ -147,7 +156,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 { @@ -248,7 +258,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" } @@ -256,41 +266,35 @@ func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (st gasPriceStr = "1" } - var ( - statedb = self.State().State() //self.chainManager.TransState() - key = self.eth.KeyManager().KeyPair() - from = statedb.GetOrNewStateObject(key.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) - msg.Sign(key.PrivateKey) - vmenv := core.NewEnv(statedb, self.chainManager, msg, block) - - res, err := vmenv.Call(from, to.Address(), data, gas, price, value) - 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), } + block := self.chainManager.CurrentBlock() + vmenv := core.NewEnv(statedb, self.chainManager, msg, block) - 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(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 { @@ -304,21 +308,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 @@ -326,3 +335,21 @@ func (self *XEth) Transact(toStr, valueStr, gasStr, gasPriceStr, codeStr string) 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 } |