Merge branch 'accounts-integration' of https://github.com/fjl/go-ethereum into fjl-accounts-integration

This commit is contained in:
obscuren 2015-03-10 11:04:11 +01:00
commit 05c9351659
38 changed files with 705 additions and 1549 deletions

View File

@ -33,6 +33,8 @@ and accounts persistence is derived from stored keys' addresses
package accounts package accounts
import ( import (
"bytes"
"crypto/ecdsa"
crand "crypto/rand" crand "crypto/rand"
"errors" "errors"
@ -42,77 +44,113 @@ import (
"github.com/ethereum/go-ethereum/crypto" "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 { type Account struct {
Address []byte Address []byte
} }
type AccountManager struct { type Manager struct {
keyStore crypto.KeyStore2 keyStore crypto.KeyStore2
unlockedKeys map[string]crypto.Key unlocked map[string]*unlocked
unlockMilliseconds time.Duration
mutex sync.RWMutex mutex sync.RWMutex
} }
func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) AccountManager { type unlocked struct {
keysMap := make(map[string]crypto.Key) *crypto.Key
am := &AccountManager{ abort chan struct{}
keyStore: keyStore,
unlockedKeys: keysMap,
unlockMilliseconds: unlockMilliseconds,
}
return *am
} }
func (am AccountManager) DeleteAccount(address []byte, auth string) error { func NewManager(keyStore crypto.KeyStore2) *Manager {
return &Manager{
keyStore: keyStore,
unlocked: make(map[string]*unlocked),
}
}
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) 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() am.mutex.RLock()
unlockedKey := am.unlockedKeys[string(fromAccount.Address)] unlockedKey, found := am.unlocked[string(a.Address)]
am.mutex.RUnlock() am.mutex.RUnlock()
if unlockedKey.Address == nil { if !found {
return nil, ErrLocked return nil, ErrLocked
} }
signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey) signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey)
return signature, err return signature, err
} }
func (am *AccountManager) SignLocked(fromAccount *Account, keyAuth string, toSign []byte) (signature []byte, err error) { // TimedUnlock unlocks the account with the given address.
key, err := am.keyStore.GetKey(fromAccount.Address, keyAuth) // 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 { if err != nil {
return nil, err return err
} }
am.mutex.RLock() u := am.addUnlocked(addr, key)
am.unlockedKeys[string(fromAccount.Address)] = *key go am.dropLater(addr, u, timeout)
am.mutex.RUnlock() return nil
go unlockLater(am, fromAccount.Address)
signature, err = crypto.Sign(toSign, key.PrivateKey)
return signature, err
} }
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) key, err := am.keyStore.GenerateNewKey(crand.Reader, auth)
if err != nil { if err != nil {
return nil, err return Account{}, err
} }
ua := &Account{ return Account{Address: key.Address}, nil
Address: key.Address,
}
return ua, err
} }
func (am *AccountManager) Accounts() ([]Account, error) { func (am *Manager) Accounts() ([]Account, error) {
addresses, err := am.keyStore.GetKeyAddresses() addresses, err := am.keyStore.GetKeyAddresses()
if err != nil { if err != nil {
return nil, err return nil, err
} }
accounts := make([]Account, len(addresses)) accounts := make([]Account, len(addresses))
for i, addr := range addresses { for i, addr := range addresses {
accounts[i] = Account{ accounts[i] = Account{
Address: addr, Address: addr,
@ -121,12 +159,47 @@ func (am *AccountManager) Accounts() ([]Account, error) {
return accounts, err return accounts, err
} }
func unlockLater(am *AccountManager, addr []byte) { func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
select { u := &unlocked{Key: key, abort: make(chan struct{})}
case <-time.After(time.Millisecond * am.unlockMilliseconds): 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()
} }

View File

@ -1,43 +1,36 @@
package accounts package accounts
import ( import (
"io/ioutil"
"os"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/ethereum/go-ethereum/crypto/randentropy"
"github.com/ethereum/go-ethereum/ethutil"
"time"
) )
func TestAccountManager(t *testing.T) { func TestSign(t *testing.T) {
ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain)
am := NewAccountManager(ks, 100) defer os.RemoveAll(dir)
am := NewManager(ks)
pass := "" // not used but required by API pass := "" // not used but required by API
a1, err := am.NewAccount(pass) a1, err := am.NewAccount(pass)
toSign := randentropy.GetEntropyCSPRNG(32) toSign := randentropy.GetEntropyCSPRNG(32)
_, err = am.SignLocked(a1, pass, toSign) am.Unlock(a1.Address, "")
if err != nil {
t.Fatal(err)
}
// Cleanup _, err = am.Sign(a1, toSign)
time.Sleep(time.Millisecond * 150) // wait for locking
accounts, err := am.Accounts()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
for _, account := range accounts {
err := am.DeleteAccount(account.Address, pass)
if err != nil {
t.Fatal(err)
}
}
} }
func TestAccountManagerLocking(t *testing.T) { func TestTimedUnlock(t *testing.T) {
ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts") dir, ks := tmpKeyStore(t, crypto.NewKeyStorePassphrase)
am := NewAccountManager(ks, 200) defer os.RemoveAll(dir)
am := NewManager(ks)
pass := "foo" pass := "foo"
a1, err := am.NewAccount(pass) a1, err := am.NewAccount(pass)
toSign := randentropy.GetEntropyCSPRNG(32) toSign := randentropy.GetEntropyCSPRNG(32)
@ -45,38 +38,32 @@ func TestAccountManagerLocking(t *testing.T) {
// Signing without passphrase fails because account is locked // Signing without passphrase fails because account is locked
_, err = am.Sign(a1, toSign) _, err = am.Sign(a1, toSign)
if err != ErrLocked { if err != ErrLocked {
t.Fatal(err) t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
} }
// Signing with passphrase works // Signing with passphrase works
_, err = am.SignLocked(a1, pass, toSign) if err = am.TimedUnlock(a1.Address, pass, 100*time.Millisecond); err != nil {
if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Signing without passphrase works because account is temp unlocked // Signing without passphrase works because account is temp unlocked
_, err = am.Sign(a1, toSign) _, err = am.Sign(a1, toSign)
if err != nil { 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 // Signing fails again after automatic locking
time.Sleep(time.Millisecond * time.Duration(250)) time.Sleep(150 * time.Millisecond)
_, err = am.Sign(a1, toSign) _, err = am.Sign(a1, toSign)
if err != ErrLocked { if err != ErrLocked {
t.Fatal(err) t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
}
// Cleanup
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 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)
}
return d, new(d)
}

View File

@ -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)
}

View File

@ -25,34 +25,26 @@ package main
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"math/big" "math/big"
"path" "os"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core"
types "github.com/ethereum/go-ethereum/core/types" types "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger" "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" "github.com/ethereum/go-ethereum/rlp"
) )
const (
ClientIdentifier = "Ethereum(G)"
Version = "0.8.6"
)
type Account struct { type Account struct {
Balance string Balance string
Code string Code string
@ -78,6 +70,7 @@ type BlockHeader struct {
TransactionsTrie string TransactionsTrie string
UncleHash string UncleHash string
} }
type Tx struct { type Tx struct {
Data string Data string
GasLimit string GasLimit string
@ -103,103 +96,44 @@ type Test struct {
Pre map[string]Account 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() { func main() {
init_vars() flag.Usage = func() {
fmt.Fprintf(os.Stderr, "%s <testfile>\n", os.Args[0])
Init() flag.PrintDefaults()
if len(TestFile) < 1 {
log.Fatal("Please specify test file")
}
blocks, err := loadBlocksFromTestFile(TestFile)
if err != nil {
panic(err)
} }
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
logger.AddLogSystem(logger.NewStdLogSystem(os.Stderr, log.LstdFlags, logger.DebugDetailLevel))
defer func() { logger.Flush() }()
defer func() { if len(os.Args) < 2 {
logger.Flush() 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") func memchain() *core.ChainManager {
blockdb, err := ethdb.NewMemDatabase()
ethereum, err := eth.New(&eth.Config{ if err != nil {
Name: p2p.MakeName(ClientIdentifier, Version), utils.Fatalf("Could not create in-memory database: %v", err)
KeyStore: KeyStore, }
DataDir: Datadir, statedb, err := ethdb.NewMemDatabase()
LogFile: LogFile, if err != nil {
LogLevel: LogLevel, utils.Fatalf("Could not create in-memory database: %v", err)
LogFormat: LogFormat, }
MaxPeers: MaxPeer, return core.NewChainManager(blockdb, statedb, new(event.TypeMux))
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 loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { 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 { if err != nil {
return return
} }
bt := *new(map[string]Test) bt := make(map[string]Test)
err = json.Unmarshal(fileContent, &bt) if err = json.Unmarshal(fileContent, &bt); err != nil {
if err != nil {
return return
} }
@ -272,49 +205,6 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) {
return 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) { func hex_decode(s string) (res []byte, err error) {
return hex.DecodeString(strings.TrimPrefix(s, "0x")) return hex.DecodeString(strings.TrimPrefix(s, "0x"))
} }

View File

@ -22,11 +22,9 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/signal"
"path" "path"
"strings" "strings"
"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/eth"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
@ -37,51 +35,118 @@ import (
"github.com/peterh/liner" "github.com/peterh/liner"
) )
func execJsFile(ethereum *eth.Ethereum, filename string) { type prompter interface {
file, err := os.Open(filename) AppendHistory(string)
if err != nil { Prompt(p string) (string, error)
utils.Fatalf("%v", err) PasswordPrompt(p string) (string, error)
}
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 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 re *javascript.JSRE
ethereum *eth.Ethereum ethereum *eth.Ethereum
xeth *xeth.XEth xeth *xeth.XEth
prompt string ps1 string
lr *liner.State
prompter
} }
func runREPL(ethereum *eth.Ethereum) { func newJSRE(ethereum *eth.Ethereum) *jsre {
xeth := xeth.New(ethereum, nil) js := &jsre{ethereum: ethereum, ps1: "> "}
repl := &repl{ js.xeth = xeth.New(ethereum, js)
re: javascript.NewJSRE(xeth), js.re = javascript.NewJSRE(js.xeth)
xeth: xeth, js.initStdFuncs()
ethereum: ethereum,
prompt: "> ",
}
repl.initStdFuncs()
if !liner.TerminalSupported() { if !liner.TerminalSupported() {
repl.dumbRead() js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
} else { } else {
lr := liner.NewLiner() lr := liner.NewLiner()
defer lr.Close()
lr.SetCtrlCAborts(true) lr.SetCtrlCAborts(true)
repl.withHistory(func(hist *os.File) { lr.ReadHistory(hist) }) defer lr.Close()
repl.read(lr) js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
repl.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(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) hist, err := os.OpenFile(path.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil { if err != nil {
fmt.Printf("unable to open history file: %v\n", err) fmt.Printf("unable to open history file: %v\n", err)
@ -91,7 +156,7 @@ func (self *repl) withHistory(op func(*os.File)) {
hist.Close() hist.Close()
} }
func (self *repl) parseInput(code string) { func (self *jsre) parseInput(code string) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
fmt.Println("[native] error", r) fmt.Println("[native] error", r)
@ -108,79 +173,21 @@ func (self *repl) parseInput(code string) {
var indentCount = 0 var indentCount = 0
var str = "" var str = ""
func (self *repl) setIndent() { func (self *jsre) setIndent() {
open := strings.Count(str, "{") open := strings.Count(str, "{")
open += strings.Count(str, "(") open += strings.Count(str, "(")
closed := strings.Count(str, "}") closed := strings.Count(str, "}")
closed += strings.Count(str, ")") closed += strings.Count(str, ")")
indentCount = open - closed indentCount = open - closed
if indentCount <= 0 { if indentCount <= 0 {
self.prompt = "> " self.ps1 = "> "
} else { } else {
self.prompt = strings.Join(make([]string, indentCount*2), "..") self.ps1 = strings.Join(make([]string, indentCount*2), "..")
self.prompt += " " self.ps1 += " "
} }
} }
func (self *repl) read(lr *liner.State) { func (self *jsre) printValue(v interface{}) {
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{}) {
method, _ := self.re.Vm.Get("prettyPrint") method, _ := self.re.Vm.Get("prettyPrint")
v, err := self.re.Vm.ToValue(v) v, err := self.re.Vm.ToValue(v)
if err == nil { 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") t, _ := self.re.Vm.Get("eth")
eth := t.Object() eth := t.Object()
eth.Set("connect", self.connect) eth.Set("connect", self.connect)
@ -205,7 +212,7 @@ func (self *repl) initStdFuncs() {
* The following methods are natively implemented javascript functions. * 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 var block *types.Block
if len(call.ArgumentList) > 0 { if len(call.ArgumentList) > 0 {
@ -236,17 +243,17 @@ func (self *repl) dump(call otto.FunctionCall) otto.Value {
return v return v
} }
func (self *repl) stopMining(call otto.FunctionCall) otto.Value { func (self *jsre) stopMining(call otto.FunctionCall) otto.Value {
self.xeth.Miner().Stop() self.xeth.Miner().Stop()
return otto.TrueValue() 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() self.xeth.Miner().Start()
return otto.TrueValue() 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() nodeURL, err := call.Argument(0).ToString()
if err != nil { if err != nil {
return otto.FalseValue() return otto.FalseValue()
@ -257,7 +264,7 @@ func (self *repl) connect(call otto.FunctionCall) otto.Value {
return otto.TrueValue() 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 { if len(call.ArgumentList) == 0 {
fmt.Println("err: require file name") fmt.Println("err: require file name")
return otto.FalseValue() return otto.FalseValue()

View File

@ -21,6 +21,7 @@
package main package main
import ( import (
"bufio"
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
@ -34,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
"github.com/peterh/liner"
) )
const ( const (
@ -43,12 +45,10 @@ const (
var ( var (
clilogger = logger.NewLogger("CLI") clilogger = logger.NewLogger("CLI")
app = cli.NewApp() app = utils.NewApp(Version, "the go-ethereum command line interface")
) )
func init() { func init() {
app.Version = Version
app.Usage = "the go-ethereum command-line client"
app.Action = run app.Action = run
app.HideVersion = true // we have a command to print the version app.HideVersion = true // we have a command to print the version
app.Commands = []cli.Command{ app.Commands = []cli.Command{
@ -60,6 +60,23 @@ func init() {
The output of this command is supposed to be machine-readable. 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, Action: dump,
Name: "dump", Name: "dump",
@ -88,13 +105,9 @@ runtime will execute the file and exit.
Usage: `import a blockchain file`, Usage: `import a blockchain file`,
}, },
} }
app.Author = ""
app.Email = ""
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
utils.BootnodesFlag, utils.BootnodesFlag,
utils.DataDirFlag, utils.DataDirFlag,
utils.KeyRingFlag,
utils.KeyStoreFlag,
utils.ListenPortFlag, utils.ListenPortFlag,
utils.LogFileFlag, utils.LogFileFlag,
utils.LogFormatFlag, utils.LogFormatFlag,
@ -144,29 +157,59 @@ func run(ctx *cli.Context) {
func runjs(ctx *cli.Context) { func runjs(ctx *cli.Context) {
eth := utils.GetEthereum(ClientIdentifier, Version, ctx) eth := utils.GetEthereum(ClientIdentifier, Version, ctx)
startEth(ctx, eth) startEth(ctx, eth)
repl := newJSRE(eth)
if len(ctx.Args()) == 0 { if len(ctx.Args()) == 0 {
runREPL(eth) repl.interactive()
} else {
for _, file := range ctx.Args() {
repl.exec(file)
}
}
eth.Stop() eth.Stop()
eth.WaitForShutdown() eth.WaitForShutdown()
} else if len(ctx.Args()) == 1 {
execJsFile(eth, ctx.Args()[0])
} else {
utils.Fatalf("This command can handle at most one argument.")
}
} }
func startEth(ctx *cli.Context, eth *eth.Ethereum) { func startEth(ctx *cli.Context, eth *eth.Ethereum) {
utils.StartEthereum(eth) utils.StartEthereum(eth)
if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
addr := ctx.GlobalString(utils.RPCListenAddrFlag.Name) utils.StartRPC(eth, ctx)
port := ctx.GlobalInt(utils.RPCPortFlag.Name)
utils.StartRpc(eth, addr, port)
} }
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
eth.Miner().Start() 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) { func importchain(ctx *cli.Context) {
if len(ctx.Args()) != 1 { if len(ctx.Args()) != 1 {
utils.Fatalf("This command requires an argument.") 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) { func version(c *cli.Context) {
fmt.Printf(`%v %v fmt.Printf(`%v %v
PV=%d PV=%d
@ -217,3 +254,24 @@ GOPATH=%s
GOROOT=%s GOROOT=%s
`, ClientIdentifier, Version, eth.ProtocolVersion, runtime.GOOS, runtime.Version(), os.Getenv("GOPATH"), runtime.GOROOT()) `, 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
}

View File

@ -59,8 +59,6 @@ func main() {
logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(*loglevel))) logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(*loglevel)))
ethutil.ReadConfig("/tmp/evmtest", "/tmp/evm", "")
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb := state.New(nil, db) statedb := state.New(nil, db)
sender := statedb.NewStateObject([]byte("sender")) sender := statedb.NewStateObject([]byte("sender"))

View File

@ -190,6 +190,11 @@ ApplicationWindow {
} }
} }
MenuItem {
text: "Generate key"
shortcut: "Ctrl+k"
onTriggered: gui.generateKey()
}
} }
Menu { Menu {

View File

@ -54,7 +54,6 @@ Rectangle {
height: 200 height: 200
anchors { anchors {
left: parent.left left: parent.left
right: logLevelSlider.left
bottom: parent.bottom bottom: parent.bottom
top: parent.top 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 { property var logModel: ListModel {

View File

@ -28,7 +28,6 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils" "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/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
) )
@ -37,19 +36,7 @@ type plugin struct {
Path string `json:"path"` Path string `json:"path"`
} }
// LogPrint writes to the GUI log. func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (string, error) {
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) {
var data string var data string
if len(recipient) == 0 { if len(recipient) == 0 {
code, err := ethutil.Compile(d, false) 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)) data = ethutil.Bytes2Hex(utils.FormatTransactionData(d))
} }
return gui.xeth.Transact(recipient, value, gas, gasPrice, data) return gui.xeth.Transact(from, recipient, value, gas, gasPrice, data)
}
// functions that allow Gui to implement interface guilogger.LogSystem
func (gui *Gui) SetLogLevel(level logger.LogLevel) {
gui.logLevel = level
gui.eth.Logger().SetLogLevel(level)
gui.config.Save("loglevel", level)
}
func (gui *Gui) GetLogLevel() logger.LogLevel {
return gui.logLevel
} }
func (self *Gui) AddPlugin(pluginPath string) { func (self *Gui) AddPlugin(pluginPath string) {
@ -89,11 +65,6 @@ func (self *Gui) RemovePlugin(pluginPath string) {
ethutil.WriteFile(self.eth.DataDir+"/plugins.json", json) 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) { func (self *Gui) DumpState(hash, path string) {
var stateDump []byte var stateDump []byte

View File

@ -137,16 +137,18 @@ func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, data
return return
} }
// TODO: improve this
allAccounts, _ := self.lib.eth.AccountManager().Accounts()
var ( var (
gas = ethutil.Big(gasStr) gas = ethutil.Big(gasStr)
gasPrice = ethutil.Big(gasPriceStr) gasPrice = ethutil.Big(gasPriceStr)
value = ethutil.Big(valueStr) value = ethutil.Big(valueStr)
// Contract addr as test address acc = allAccounts[0]
keyPair = self.lib.eth.KeyManager().KeyPair()
) )
statedb := self.lib.eth.ChainManager().TransState() 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 := statedb.NewStateObject([]byte{0})
contract.SetCode(script) contract.SetCode(script)
contract.SetBalance(value) contract.SetBalance(value)

View File

@ -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)
}
}

View File

@ -23,7 +23,6 @@ package main
import "C" import "C"
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -70,20 +69,18 @@ type Gui struct {
txDb *ethdb.LDBDatabase txDb *ethdb.LDBDatabase
logLevel logger.LogLevel
open bool open bool
xeth *xeth.XEth xeth *xeth.XEth
Session string Session string
config *ethutil.ConfigManager
plugins map[string]plugin plugins map[string]plugin
} }
// Create GUI, but doesn't start it // Create GUI, but doesn't start it
func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session string, logLevel int) *Gui { func NewWindow(ethereum *eth.Ethereum) *Gui {
db, err := ethdb.NewLDBDatabase("tx_database") db, err := ethdb.NewLDBDatabase(path.Join(ethereum.DataDir, "tx_database"))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -92,10 +89,7 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session st
gui := &Gui{eth: ethereum, gui := &Gui{eth: ethereum,
txDb: db, txDb: db,
xeth: xeth, xeth: xeth,
logLevel: logger.LogLevel(logLevel),
Session: session,
open: false, open: false,
config: config,
plugins: make(map[string]plugin), plugins: make(map[string]plugin),
serviceEvents: make(chan ServEv, 1), serviceEvents: make(chan ServEv, 1),
} }
@ -142,18 +136,12 @@ func (gui *Gui) Start(assetPath string) {
gui.open = true gui.open = true
win.Show() win.Show()
// only add the gui guilogger after window is shown otherwise slider wont be shown
logger.AddLogSystem(gui)
win.Wait() 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 gui.open = false
} }
func (gui *Gui) Stop() { func (gui *Gui) Stop() {
if gui.open { if gui.open {
gui.logLevel = logger.Silence
gui.open = false gui.open = false
gui.win.Hide() gui.win.Hide()
} }
@ -172,7 +160,11 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
return gui.win, nil 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) { 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 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) { func (gui *Gui) setInitialChain(ancientBlocks bool) {
sBlk := gui.eth.ChainManager().LastBlockHash() sBlk := gui.eth.ChainManager().LastBlockHash()
blk := gui.eth.ChainManager().GetBlock(sBlk) blk := gui.eth.ChainManager().GetBlock(sBlk)
for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) { for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) {
sBlk = blk.ParentHash() sBlk = blk.ParentHash()
gui.processBlock(blk, true) gui.processBlock(blk, true)
} }
} }
@ -259,10 +231,8 @@ func (self *Gui) loadMergedMiningOptions() {
} }
func (gui *Gui) insertTransaction(window string, tx *types.Transaction) { func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
addr := gui.address()
var inout string var inout string
if bytes.Compare(tx.From(), addr) == 0 { if gui.eth.AccountManager().HasAccount(tx.From()) {
inout = "send" inout = "send"
} else { } else {
inout = "recv" 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) { func LoadExtension(path string) (uintptr, error) {
lib, err := ffi.NewLibrary(path) lib, err := ffi.NewLibrary(path)

View File

@ -26,10 +26,10 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/cmd/utils" "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/logger"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/ui/qt/webengine" "github.com/ethereum/go-ethereum/ui/qt/webengine"
"github.com/obscuren/qml" "github.com/obscuren/qml"
) )
@ -39,56 +39,32 @@ const (
Version = "0.9.0" Version = "0.9.0"
) )
var ethereum *eth.Ethereum var (
var mainlogger = logger.NewLogger("MAIN") app = utils.NewApp(Version, "the ether browser")
assetPathFlag = cli.StringFlag{
func run() error { Name: "asset_path",
webengine.Initialize() Usage: "absolute path to GUI assets directory",
Value: ethutil.DefaultAssetPath(),
// 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(&eth.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)
} }
utils.KeyTasks(ethereum.KeyManager(), KeyRing, GenAddr, SecretFile, ExportDir, NonInteractive) )
if StartRpc { func init() {
utils.StartRpc(ethereum, RpcListenAddress, RpcPort) 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() { func main() {
@ -97,15 +73,16 @@ func main() {
// This is a bit of a cheat, but ey! // This is a bit of a cheat, but ey!
os.Setenv("QTWEBKIT_INSPECTOR_SERVER", "127.0.0.1:99999") os.Setenv("QTWEBKIT_INSPECTOR_SERVER", "127.0.0.1:99999")
qml.Run(run)
var interrupted = false var interrupted = false
utils.RegisterInterrupt(func(os.Signal) { utils.RegisterInterrupt(func(os.Signal) {
interrupted = true interrupted = true
}) })
utils.HandleInterrupt() 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 // we need to run the interrupt callbacks in case gui is closed
// this skips if we got here by actual interrupt stopping the GUI // this skips if we got here by actual interrupt stopping the GUI
if !interrupted { if !interrupted {
@ -113,3 +90,23 @@ func main() {
} }
logger.Flush() 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
})
}

View File

@ -153,6 +153,7 @@ func (self *UiLib) Transact(params map[string]interface{}) (string, error) {
object := mapToTxParams(params) object := mapToTxParams(params)
return self.XEth.Transact( return self.XEth.Transact(
object["from"],
object["to"], object["to"],
object["value"], object["value"],
object["gas"], object["gas"],

View File

@ -29,14 +29,11 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/rlp" "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/state"
"github.com/ethereum/go-ethereum/xeth"
) )
var clilogger = logger.NewLogger("CLI") 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) { func exit(err error) {
status := 0 status := 0
if err != nil { 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 { func FormatTransactionData(data string) []byte {
d := ethutil.StringToByteFunc(data, func(s string) (ret []byte) { d := ethutil.StringToByteFunc(data, func(s string) (ret []byte) {
slice := regexp.MustCompile("\\n|\\s").Split(s, 1000000000) slice := regexp.MustCompile("\\n|\\s").Split(s, 1000000000)

View File

@ -2,10 +2,15 @@ package utils
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt"
"net"
"net/http"
"os"
"path" "path"
"runtime" "runtime"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
@ -15,8 +20,21 @@ import (
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/nat" "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. // These are all the command line flags we support.
// If you add to this list, please remember to include the // If you add to this list, please remember to include the
// flag in the appropriate command definition. // flag in the appropriate command definition.
@ -36,16 +54,6 @@ var (
Name: "vmdebug", Name: "vmdebug",
Usage: "Virtual Machine debug output", 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{ DataDirFlag = cli.StringFlag{
Name: "datadir", Name: "datadir",
Usage: "Data directory to be used", Usage: "Data directory to be used",
@ -152,19 +160,17 @@ func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) {
func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum { func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum {
ethereum, err := eth.New(&eth.Config{ ethereum, err := eth.New(&eth.Config{
Name: p2p.MakeName(clientID, version), Name: p2p.MakeName(clientID, version),
KeyStore: ctx.GlobalString(KeyStoreFlag.Name),
DataDir: ctx.GlobalString(DataDirFlag.Name), DataDir: ctx.GlobalString(DataDirFlag.Name),
LogFile: ctx.GlobalString(LogFileFlag.Name), LogFile: ctx.GlobalString(LogFileFlag.Name),
LogLevel: ctx.GlobalInt(LogLevelFlag.Name), LogLevel: ctx.GlobalInt(LogLevelFlag.Name),
LogFormat: ctx.GlobalString(LogFormatFlag.Name), LogFormat: ctx.GlobalString(LogFormatFlag.Name),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
AccountManager: GetAccountManager(ctx),
VmDebug: ctx.GlobalBool(VMDebugFlag.Name), VmDebug: ctx.GlobalBool(VMDebugFlag.Name),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
Port: ctx.GlobalString(ListenPortFlag.Name), Port: ctx.GlobalString(ListenPortFlag.Name),
NAT: GetNAT(ctx), NAT: GetNAT(ctx),
NodeKey: GetNodeKey(ctx), NodeKey: GetNodeKey(ctx),
KeyRing: ctx.GlobalString(KeyRingFlag.Name),
Shh: true, Shh: true,
Dial: true, Dial: true,
BootNodes: ctx.GlobalString(BootnodesFlag.Name), BootNodes: ctx.GlobalString(BootnodesFlag.Name),
@ -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 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))
}

View File

@ -12,14 +12,12 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
func init() { func init() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
ethutil.ReadConfig("/tmp/ethtest", "/tmp/ethtest", "ETH")
} }
// Test fork of length N starting from block i // Test fork of length N starting from block i

View File

@ -62,8 +62,6 @@ func (tm *TestManager) Db() ethutil.Database {
} }
func NewTestManager() *TestManager { func NewTestManager() *TestManager {
ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "ETH")
db, err := ethdb.NewMemDatabase() db, err := ethdb.NewMemDatabase()
if err != nil { if err != nil {
fmt.Println("Could not create mem-db, failing") fmt.Println("Could not create mem-db, failing")

View File

@ -1,7 +1,6 @@
package core package core
import ( import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -14,7 +13,6 @@ type Backend interface {
PeerCount() int PeerCount() int
IsListening() bool IsListening() bool
Peers() []*p2p.Peer Peers() []*p2p.Peer
KeyManager() *crypto.KeyManager
BlockDb() ethutil.Database BlockDb() ethutil.Database
StateDb() ethutil.Database StateDb() ethutil.Database
EventMux() *event.TypeMux EventMux() *event.TypeMux

View File

@ -45,8 +45,6 @@ type StateTransition struct {
} }
type Message interface { type Message interface {
Hash() []byte
From() []byte From() []byte
To() []byte To() []byte
@ -153,7 +151,7 @@ func (self *StateTransition) preCheck() (err error) {
} }
func (self *StateTransition) TransitionState() (ret []byte, 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. // XXX Transactions after this point are considered valid.
if err = self.preCheck(); err != nil { if err = self.preCheck(); err != nil {

View File

@ -129,6 +129,7 @@ func (tx *Transaction) sender() []byte {
return crypto.Sha3(pubkey[1:])[12:] return crypto.Sha3(pubkey[1:])[12:]
} }
// TODO: deprecate after new accounts & key stores are integrated
func (tx *Transaction) Sign(privk []byte) error { func (tx *Transaction) Sign(privk []byte) error {
sig := tx.Signature(privk) sig := tx.Signature(privk)
@ -140,6 +141,13 @@ func (tx *Transaction) Sign(privk []byte) error {
return nil 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 { func (tx *Transaction) SignECDSA(key *ecdsa.PrivateKey) error {
return tx.Sign(crypto.FromECDSA(key)) return tx.Sign(crypto.FromECDSA(key))
} }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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())
// // }
// }

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/ethereum/ethash" "github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/blockpool" "github.com/ethereum/go-ethereum/blockpool"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -19,13 +20,12 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/vm" "github.com/ethereum/go-ethereum/vm"
"github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/whisper"
) )
var ( var (
ethlogger = logger.NewLogger("SERV") servlogger = logger.NewLogger("SERV")
jsonlogger = logger.NewJsonLogger() jsonlogger = logger.NewJsonLogger()
defaultBootNodes = []*discover.Node{ defaultBootNodes = []*discover.Node{
@ -38,11 +38,9 @@ var (
type Config struct { type Config struct {
Name string Name string
KeyStore string
DataDir string DataDir string
LogFile string LogFile string
LogLevel int LogLevel int
KeyRing string
LogFormat string LogFormat string
VmDebug bool VmDebug bool
@ -62,8 +60,7 @@ type Config struct {
Dial bool Dial bool
MinerThreads int MinerThreads int
AccountManager *accounts.Manager
KeyManager *crypto.KeyManager
} }
func (cfg *Config) parseBootNodes() []*discover.Node { func (cfg *Config) parseBootNodes() []*discover.Node {
@ -77,7 +74,7 @@ func (cfg *Config) parseBootNodes() []*discover.Node {
} }
n, err := discover.ParseNode(url) n, err := discover.ParseNode(url)
if err != nil { if err != nil {
ethlogger.Errorf("Bootstrap URL %s: %v\n", url, err) servlogger.Errorf("Bootstrap URL %s: %v\n", url, err)
continue continue
} }
ns = append(ns, n) 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) return nil, fmt.Errorf("could not generate server key: %v", err)
} }
if err := ioutil.WriteFile(keyfile, crypto.FromECDSA(key), 0600); err != nil { 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 return key, nil
} }
@ -120,6 +117,7 @@ type Ethereum struct {
txPool *core.TxPool txPool *core.TxPool
chainManager *core.ChainManager chainManager *core.ChainManager
blockPool *blockpool.BlockPool blockPool *blockpool.BlockPool
accountManager *accounts.Manager
whisper *whisper.Whisper whisper *whisper.Whisper
net *p2p.Server net *p2p.Server
@ -128,9 +126,6 @@ type Ethereum struct {
blockSub event.Subscription blockSub event.Subscription
miner *miner.Miner miner *miner.Miner
RpcServer rpc.RpcServer
keyManager *crypto.KeyManager
logger logger.LogSystem logger logger.LogSystem
Mining bool Mining bool
@ -139,7 +134,7 @@ type Ethereum struct {
func New(config *Config) (*Ethereum, error) { func New(config *Config) (*Ethereum, error) {
// Boostrap database // 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")) blockDb, err := ethdb.NewLDBDatabase(path.Join(config.DataDir, "blockchain"))
if err != nil { if err != nil {
@ -158,19 +153,6 @@ func New(config *Config) (*Ethereum, error) {
return nil, fmt.Errorf("Database version mismatch. Protocol(%d / %d). `rm -rf %s`", protov, ProtocolVersion, path) 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) saveProtocolVersion(blockDb)
//ethutil.Config.Db = db //ethutil.Config.Db = db
@ -178,20 +160,24 @@ func New(config *Config) (*Ethereum, error) {
shutdownChan: make(chan bool), shutdownChan: make(chan bool),
blockDb: blockDb, blockDb: blockDb,
stateDb: stateDb, stateDb: stateDb,
keyManager: keyManager,
eventMux: &event.TypeMux{}, eventMux: &event.TypeMux{},
logger: ethlogger, logger: servlogger,
accountManager: config.AccountManager,
DataDir: config.DataDir, 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()) eth.chainManager = core.NewChainManager(blockDb, stateDb, eth.EventMux())
pow := ethash.New(eth.chainManager) pow := ethash.New(eth.chainManager)
eth.txPool = core.NewTxPool(eth.EventMux()) eth.txPool = core.NewTxPool(eth.EventMux())
eth.blockProcessor = core.NewBlockProcessor(stateDb, pow, eth.txPool, eth.chainManager, eth.EventMux()) eth.blockProcessor = core.NewBlockProcessor(stateDb, pow, eth.txPool, eth.chainManager, eth.EventMux())
eth.chainManager.SetProcessor(eth.blockProcessor) eth.chainManager.SetProcessor(eth.blockProcessor)
eth.whisper = whisper.New() 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 hasBlock := eth.chainManager.HasBlock
insertChain := eth.chainManager.InsertChain insertChain := eth.chainManager.InsertChain
@ -225,9 +211,9 @@ func New(config *Config) (*Ethereum, error) {
return eth, nil return eth, nil
} }
func (s *Ethereum) KeyManager() *crypto.KeyManager { return s.keyManager }
func (s *Ethereum) Logger() logger.LogSystem { return s.logger } func (s *Ethereum) Logger() logger.LogSystem { return s.logger }
func (s *Ethereum) Name() string { return s.net.Name } 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) ChainManager() *core.ChainManager { return s.chainManager }
func (s *Ethereum) BlockProcessor() *core.BlockProcessor { return s.blockProcessor } func (s *Ethereum) BlockProcessor() *core.BlockProcessor { return s.blockProcessor }
func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } 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) PeerCount() int { return s.net.PeerCount() }
func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() }
func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers }
func (s *Ethereum) Coinbase() []byte { return nil } // TODO
// Start the ethereum // Start the ethereum
func (s *Ethereum) Start() error { func (s *Ethereum) Start() error {
@ -271,7 +256,7 @@ func (s *Ethereum) Start() error {
s.blockSub = s.eventMux.Subscribe(core.NewMinedBlockEvent{}) s.blockSub = s.eventMux.Subscribe(core.NewMinedBlockEvent{})
go s.blockBroadcastLoop() go s.blockBroadcastLoop()
ethlogger.Infoln("Server started") servlogger.Infoln("Server started")
return nil return nil
} }
@ -292,10 +277,6 @@ func (s *Ethereum) Stop() {
s.txSub.Unsubscribe() // quits txBroadcastLoop s.txSub.Unsubscribe() // quits txBroadcastLoop
s.blockSub.Unsubscribe() // quits blockBroadcastLoop s.blockSub.Unsubscribe() // quits blockBroadcastLoop
if s.RpcServer != nil {
s.RpcServer.Stop()
}
s.txPool.Stop() s.txPool.Stop()
s.eventMux.Stop() s.eventMux.Stop()
s.blockPool.Stop() s.blockPool.Stop()
@ -303,7 +284,7 @@ func (s *Ethereum) Stop() {
s.whisper.Stop() s.whisper.Stop()
} }
ethlogger.Infoln("Server stopped") servlogger.Infoln("Server stopped")
close(s.shutdownChan) close(s.shutdownChan)
} }

View File

@ -7,8 +7,6 @@ import (
) )
func TestCompression(t *testing.T) { func TestCompression(t *testing.T) {
ethutil.ReadConfig("", "/tmp", "")
db, err := NewLDBDatabase("testdb") db, err := NewLDBDatabase("testdb")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
"github.com/obscuren/otto" "github.com/obscuren/otto"

View File

@ -70,8 +70,8 @@ func (self *JSEthereum) GetStateObject(addr string) otto.Value {
return self.toVal(&JSStateObject{self.XEth.State().SafeGet(addr), self}) return self.toVal(&JSStateObject{self.XEth.State().SafeGet(addr), self})
} }
func (self *JSEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value { func (self *JSEthereum) Transact(fromStr, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value {
r, err := self.XEth.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr) r, err := self.XEth.Transact(fromStr, recipient, valueStr, gasStr, gasPriceStr, dataStr)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)

View File

@ -10,6 +10,7 @@ package rpc
import ( import (
"math/big" "math/big"
"path"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -53,8 +54,8 @@ type EthereumApi struct {
defaultBlockAge int64 defaultBlockAge int64
} }
func NewEthereumApi(eth *xeth.XEth) *EthereumApi { func NewEthereumApi(eth *xeth.XEth, dataDir string) *EthereumApi {
db, _ := ethdb.NewLDBDatabase("dapps") db, _ := ethdb.NewLDBDatabase(path.Join(dataDir, "dapps"))
api := &EthereumApi{ api := &EthereumApi{
eth: eth, eth: eth,
mux: eth.Backend().EventMux(), 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 { 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() args.Gas = defaultGas.String()
} }
if len(args.GasPrice) == 0 { if ethutil.Big(args.GasPrice).Cmp(big.NewInt(0)) == 0 {
args.GasPrice = defaultGasPrice.String() args.GasPrice = defaultGasPrice.String()
} }
// TODO if no_private_key then result, _ := p.xeth().Transact(args.From, args.To, args.Value, args.Gas, args.GasPrice, args.Data)
//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)
*reply = result *reply = result
//}
return nil return nil
} }
func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error { func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error {
result, err := p.xeth().Call( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data) result, err := p.xeth().Call(args.From, args.To, args.Value, args.Gas, args.GasPrice, args.Data)
if err != nil { if err != nil {
return err return err
} }

View File

@ -24,6 +24,7 @@ func (obj *GetBlockArgs) UnmarshalJSON(b []byte) (err error) {
type NewTxArgs struct { type NewTxArgs struct {
From string `json:"from"` From string `json:"from"`
Pass string `json:"pass"`
To string `json:"to"` To string `json:"to"`
Value string `json:"value"` Value string `json:"value"`
Gas string `json:"gas"` Gas string `json:"gas"`

52
rpc/http.go Normal file
View File

@ -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})
})
}

View File

@ -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)
}

View File

@ -33,16 +33,16 @@ func (s *StateSuite) TestDump(c *checker.C) {
// check that dump contains the state objects that are in trie // check that dump contains the state objects that are in trie
got := string(s.state.Dump()) got := string(s.state.Dump())
want := `{ want := `{
"root": "4e3a59299745ba6752247c8b91d0f716dac9ec235861c91f5ac1894a361d87ba", "root": "6e277ae8357d013e50f74eedb66a991f6922f93ae03714de58b3d0c5e9eee53f",
"accounts": { "accounts": {
"0000000000000000000000000000000000000001": { "1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d": {
"balance": "22", "balance": "22",
"nonce": 0, "nonce": 0,
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"storage": {} "storage": {}
}, },
"0000000000000000000000000000000000000102": { "a17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1": {
"balance": "0", "balance": "0",
"nonce": 0, "nonce": 0,
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
@ -57,7 +57,6 @@ func (s *StateSuite) TestDump(c *checker.C) {
} }
func (s *StateSuite) SetUpTest(c *checker.C) { func (s *StateSuite) SetUpTest(c *checker.C) {
ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "")
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
s.state = New(nil, db) s.state = New(nil, db)
} }

View File

@ -4,7 +4,6 @@ import (
"log" "log"
"os" "os"
"github.com/ethereum/go-ethereum/ethutil"
logpkg "github.com/ethereum/go-ethereum/logger" logpkg "github.com/ethereum/go-ethereum/logger"
) )
@ -14,6 +13,4 @@ var Log = logpkg.NewLogger("TEST")
func init() { func init() {
Logger = logpkg.NewStdLogSystem(os.Stdout, log.LstdFlags, logpkg.InfoLevel) Logger = logpkg.NewStdLogSystem(os.Stdout, log.LstdFlags, logpkg.InfoLevel)
logpkg.AddLogSystem(Logger) logpkg.AddLogSystem(Logger)
ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "")
} }

View File

@ -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
}

View File

@ -1,14 +1,13 @@
// eXtended ETHereum
package xeth package xeth
/*
* eXtended ETHereum
*/
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -18,7 +17,6 @@ import (
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/ui"
"github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/whisper"
) )
@ -28,11 +26,11 @@ var pipelogger = logger.NewLogger("XETH")
type Backend interface { type Backend interface {
BlockProcessor() *core.BlockProcessor BlockProcessor() *core.BlockProcessor
ChainManager() *core.ChainManager ChainManager() *core.ChainManager
AccountManager() *accounts.Manager
TxPool() *core.TxPool TxPool() *core.TxPool
PeerCount() int PeerCount() int
IsListening() bool IsListening() bool
Peers() []*p2p.Peer Peers() []*p2p.Peer
KeyManager() *crypto.KeyManager
BlockDb() ethutil.Database BlockDb() ethutil.Database
StateDb() ethutil.Database StateDb() ethutil.Database
EventMux() *event.TypeMux EventMux() *event.TypeMux
@ -40,37 +38,62 @@ type Backend interface {
Miner() *miner.Miner 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 { type XEth struct {
eth Backend eth Backend
blockProcessor *core.BlockProcessor blockProcessor *core.BlockProcessor
chainManager *core.ChainManager chainManager *core.ChainManager
accountManager *accounts.Manager
state *State state *State
whisper *Whisper whisper *Whisper
miner *miner.Miner 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 (dummyFrontend) UnlockAccount([]byte) bool { return false }
func (TmpFrontend) ConfirmTransaction(*types.Transaction) bool { panic("CONFIRM TRANSACTION") } 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{ xeth := &XEth{
eth: eth, eth: eth,
blockProcessor: eth.BlockProcessor(), blockProcessor: eth.BlockProcessor(),
chainManager: eth.ChainManager(), chainManager: eth.ChainManager(),
accountManager: eth.AccountManager(),
whisper: NewWhisper(eth.Whisper()), whisper: NewWhisper(eth.Whisper()),
miner: eth.Miner(), miner: eth.Miner(),
frontend: frontend,
} }
if frontend == nil { if frontend == nil {
xeth.frontend = TmpFrontend{} xeth.frontend = dummyFrontend{}
} }
xeth.state = NewState(xeth, xeth.chainManager.TransState()) xeth.state = NewState(xeth, xeth.chainManager.TransState())
return xeth return xeth
} }
@ -120,7 +143,13 @@ func (self *XEth) Block(v interface{}) *Block {
} }
func (self *XEth) Accounts() []string { 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 { func (self *XEth) PeerCount() int {
@ -147,7 +176,8 @@ func (self *XEth) IsListening() bool {
} }
func (self *XEth) Coinbase() string { 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 { func (self *XEth) NumberToHuman(balance string) string {
@ -248,7 +278,7 @@ func (self *XEth) PushTx(encodedTx string) (string, error) {
return toHex(tx.Hash()), nil 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 { if len(gasStr) == 0 {
gasStr = "100000" gasStr = "100000"
} }
@ -256,41 +286,34 @@ func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (st
gasPriceStr = "1" gasPriceStr = "1"
} }
var ( statedb := self.State().State() //self.chainManager.TransState()
statedb = self.State().State() //self.chainManager.TransState() msg := callmsg{
key = self.eth.KeyManager().KeyPair() from: statedb.GetOrNewStateObject(fromHex(fromStr)),
from = statedb.GetOrNewStateObject(key.Address()) to: fromHex(toStr),
block = self.chainManager.CurrentBlock() gas: ethutil.Big(gasStr),
to = statedb.GetOrNewStateObject(fromHex(toStr)) gasPrice: ethutil.Big(gasPriceStr),
data = fromHex(dataStr) value: ethutil.Big(valueStr),
gas = ethutil.Big(gasStr) data: fromHex(dataStr),
price = ethutil.Big(gasPriceStr) }
value = ethutil.Big(valueStr) block := self.chainManager.CurrentBlock()
)
msg := types.NewTransactionMessage(fromHex(toStr), value, gas, price, data)
msg.Sign(key.PrivateKey)
vmenv := core.NewEnv(statedb, self.chainManager, msg, block) vmenv := core.NewEnv(statedb, self.chainManager, msg, block)
res, err := vmenv.Call(from, to.Address(), data, gas, price, value) res, err := vmenv.Call(msg.from, msg.to, msg.data, msg.gas, msg.gasPrice, msg.value)
if err != nil { return toHex(res), err
return "", err
}
return toHex(res), nil
} }
func (self *XEth) Transact(toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) {
var ( var (
from []byte
to []byte to []byte
value = ethutil.NewValue(valueStr) value = ethutil.NewValue(valueStr)
gas = ethutil.NewValue(gasStr) gas = ethutil.NewValue(gasStr)
price = ethutil.NewValue(gasPriceStr) price = ethutil.NewValue(gasPriceStr)
data []byte data []byte
key = self.eth.KeyManager().KeyPair()
contractCreation bool contractCreation bool
) )
from = fromHex(fromStr)
data = fromHex(codeStr) data = fromHex(codeStr)
to = fromHex(toStr) to = fromHex(toStr)
if len(to) == 0 { 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) tx = types.NewTransactionMessage(to, value.BigInt(), gas.BigInt(), price.BigInt(), data)
} }
var err error state := self.chainManager.TransState()
state := self.eth.ChainManager().TxState() nonce := state.GetNonce(from)
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())
tx.SetNonce(nonce) tx.SetNonce(nonce)
tx.Sign(key.PrivateKey)
err = self.eth.TxPool().Add(tx) if err := self.sign(tx, from, false); err != nil {
if err != nil {
return "", err 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) { if types.IsContractAddr(to) {
return toHex(core.AddressFromMessage(tx)), nil return toHex(core.AddressFromMessage(tx)), nil
} }
return toHex(tx.Hash()), 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 }