diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 3e9fa7799..fdd7d83e9 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,113 @@ 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 + mutex sync.RWMutex } -func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) AccountManager { - keysMap := make(map[string]crypto.Key) - am := &AccountManager{ - keyStore: keyStore, - unlockedKeys: keysMap, - unlockMilliseconds: unlockMilliseconds, +type unlocked struct { + *crypto.Key + abort chan struct{} +} + +func NewManager(keyStore crypto.KeyStore2) *Manager { + return &Manager{ + keyStore: keyStore, + unlocked: make(map[string]*unlocked), } - return *am } -func (am AccountManager) DeleteAccount(address []byte, auth string) error { +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 addrs[0], nil +} + +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) +// 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 } - am.mutex.RLock() - am.unlockedKeys[string(fromAccount.Address)] = *key - am.mutex.RUnlock() - go unlockLater(am, fromAccount.Address) - signature, err = crypto.Sign(toSign, key.PrivateKey) - return signature, err + u := am.addUnlocked(addr, key) + go am.dropLater(addr, u, timeout) + return nil } -func (am AccountManager) NewAccount(auth string) (*Account, error) { +// 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) { 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) { +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 +159,47 @@ func (am *AccountManager) Accounts() ([]Account, error) { return accounts, err } -func unlockLater(am *AccountManager, addr []byte) { - select { - case <-time.After(time.Millisecond * am.unlockMilliseconds): +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) + // 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 + am.mutex.Unlock() + return u +} + +func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) { + t := time.NewTimer(timeout) + defer t.Stop() + select { + 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..427114cbd 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -1,43 +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" - "time" ) -func TestAccountManager(t *testing.T) { - ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 100) +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(time.Millisecond * 150) // 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 := NewAccountManager(ks, 200) +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) @@ -45,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(time.Millisecond * time.Duration(250)) - + // 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/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..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() - - 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 { + 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/js.go b/cmd/ethereum/js.go index e3165d3f5..de73e83a2 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,118 @@ 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 dumbterm struct{ r *bufio.Reader } + +func (r dumbterm) Prompt(p string) (string, error) { + fmt.Print(p) + return r.r.ReadString('\n') +} + +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') + fmt.Println() + return input, err +} + +func (r dumbterm) 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 = dumbterm{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.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.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.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.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 +156,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 +173,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 +198,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 +212,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 +243,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 +264,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 8b361f7ae..1703c02bb 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{ @@ -60,6 +60,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", @@ -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, @@ -144,29 +157,59 @@ 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) - eth.Stop() - eth.WaitForShutdown() - } else if len(ctx.Args()) == 1 { - execJsFile(eth, ctx.Args()[0]) + repl.interactive() } else { - utils.Fatalf("This command can handle at most one argument.") + for _, file := range ctx.Args() { + repl.exec(file) + } } + eth.Stop() + eth.WaitForShutdown() } 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 +245,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 +254,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/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/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 . -*/ -/** - * @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 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..cde5fa024 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2,10 +2,15 @@ package utils import ( "crypto/ecdsa" + "fmt" + "net" + "net/http" + "os" "path" "runtime" "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 +20,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 +54,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 +159,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 +194,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) +} + +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/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/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/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()) -// // } -// } diff --git a/eth/backend.go b/eth/backend.go index 584d60c7e..ac8a016fe 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,13 +20,12 @@ 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" ) var ( - ethlogger = logger.NewLogger("SERV") + servlogger = logger.NewLogger("SERV") jsonlogger = logger.NewJsonLogger() defaultBootNodes = []*discover.Node{ @@ -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 { @@ -77,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) @@ -101,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 } @@ -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 @@ -139,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 { @@ -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: servlogger, + 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 { @@ -271,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 } @@ -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() @@ -303,7 +284,7 @@ func (s *Ethereum) Stop() { s.whisper.Stop() } - ethlogger.Infoln("Server stopped") + servlogger.Infoln("Server stopped") close(s.shutdownChan) } 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/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 . -*/ -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/state/state_test.go b/state/state_test.go index ee1cf9286..07e35f7e2 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", @@ -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", "") } 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 88ae253cd..f8b537321 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -1,14 +1,13 @@ +// eXtended ETHereum package xeth -/* - * eXtended ETHereum - */ - 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" @@ -18,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" ) @@ -28,11 +26,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 @@ -40,37 +38,62 @@ 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 chainManager *core.ChainManager + accountManager *accounts.Manager state *State 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(), chainManager: eth.ChainManager(), + 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 } @@ -120,7 +143,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 +176,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 +278,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 +286,34 @@ 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) + 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) - 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(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,25 +327,61 @@ 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) - err = self.eth.TxPool().Add(tx) - if err != nil { + if err := self.sign(tx, from, false); err != nil { return "", err } - state.SetNonce(key.Address(), nonce+1) + if err := self.eth.TxPool().Add(tx); err != nil { + return "", err + } + 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 } - 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 + 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 }