Merge branch 'develop' into rpcfrontier

Conflicts:
	rpc/api.go
	rpc/args.go
This commit is contained in:
obscuren 2015-03-11 01:08:42 +01:00
commit 7e0ccc9de5
42 changed files with 847 additions and 1564 deletions

4
Godeps/Godeps.json generated
View File

@ -17,8 +17,8 @@
},
{
"ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0-74-g50c77ec",
"Rev": "50c77ecec0068c9aef9d90ae0fd0fdf410041da3"
"Comment": "1.2.0-81-g3e09053",
"Rev": "3e0905345cd2c5366530dbcdce62457f2ce16e7c"
},
{
"ImportPath": "github.com/ethereum/ethash",

View File

@ -43,9 +43,11 @@ type App struct {
CommandNotFound func(context *Context, command string)
// Compilation date
Compiled time.Time
// Author
// List of all authors who contributed
Authors []Author
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string
// Author e-mail
// Email of Author (Note: Use App.Authors, this is deprecated)
Email string
// Writer writer to write output to
Writer io.Writer
@ -70,14 +72,19 @@ func NewApp() *App {
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Author: "Author",
Email: "unknown@email",
Author: "Dr. James",
Email: "who@gmail.com",
Authors: []Author{{"Jim", "jim@corporate.com"}, {"Hank", "hank@indiepalace.com"}},
Writer: os.Stdout,
}
}
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
if a.Author != "" && a.Author != "" {
a.Authors = append(a.Authors, Author{a.Author, a.Email})
}
if HelpPrinter == nil {
defer func() {
HelpPrinter = nil
@ -294,3 +301,19 @@ func (a *App) appendFlag(flag Flag) {
a.Flags = append(a.Flags, flag)
}
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Email string // The Authors email
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
e := ""
if a.Email != "" {
e = "<" + a.Email + "> "
}
return fmt.Sprintf("%v %v", a.Name, e)
}

View File

@ -21,6 +21,9 @@ func ExampleApp() {
app.Action = func(c *cli.Context) {
fmt.Printf("Hello %v\n", c.String("name"))
}
app.Author = "Harrison"
app.Email = "harrison@lolwut.com"
app.Authors = []cli.Author{{"Oliver Allen", "oliver@toyshop.com"}}
app.Run(os.Args)
// Output:
// Hello Jeremy

View File

@ -119,7 +119,7 @@ func (c Command) Run(ctx *Context) error {
// Returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
return c.Name == name || c.ShortName == name
return c.Name == name || (c.ShortName != "" && c.ShortName == name)
}
func (c Command) startApp(ctx *Context) error {

View File

@ -12,11 +12,10 @@ USAGE:
{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
VERSION:
{{.Version}}{{if or .Author .Email}}
{{.Version}}
AUTHOR:{{if .Author}}
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
{{.Email}}{{end}}{{end}}
AUTHOR(S):
{{range .Authors}}{{ . }} {{end}}
COMMANDS:
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
@ -112,6 +111,12 @@ func DefaultAppComplete(c *Context) {
// Prints help for the given command
func ShowCommandHelp(c *Context, command string) {
// show the subcommand help for a command with subcommands
if command == "" {
HelpPrinter(SubcommandHelpTemplate, c.App)
return
}
for _, c := range c.App.Commands {
if c.HasName(command) {
HelpPrinter(CommandHelpTemplate, c)

View File

@ -33,7 +33,10 @@ and accounts persistence is derived from stored keys' addresses
package accounts
import (
"bytes"
"crypto/ecdsa"
crand "crypto/rand"
"os"
"errors"
"sync"
@ -42,77 +45,117 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)
var ErrLocked = errors.New("account is locked; please request passphrase")
var (
ErrLocked = errors.New("account is locked")
ErrNoKeys = errors.New("no keys in store")
)
// TODO: better name for this struct?
type Account struct {
Address []byte
}
type AccountManager struct {
keyStore crypto.KeyStore2
unlockedKeys map[string]crypto.Key
unlockMilliseconds time.Duration
mutex sync.RWMutex
type Manager struct {
keyStore crypto.KeyStore2
unlocked map[string]*unlocked
mutex sync.RWMutex
}
func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) AccountManager {
keysMap := make(map[string]crypto.Key)
am := &AccountManager{
keyStore: keyStore,
unlockedKeys: keysMap,
unlockMilliseconds: unlockMilliseconds,
type unlocked struct {
*crypto.Key
abort chan struct{}
}
func NewManager(keyStore crypto.KeyStore2) *Manager {
return &Manager{
keyStore: keyStore,
unlocked: make(map[string]*unlocked),
}
return *am
}
func (am AccountManager) DeleteAccount(address []byte, auth string) error {
func (am *Manager) HasAccount(addr []byte) bool {
accounts, _ := am.Accounts()
for _, acct := range accounts {
if bytes.Compare(acct.Address, addr) == 0 {
return true
}
}
return false
}
// Coinbase returns the account address that mining rewards are sent to.
func (am *Manager) Coinbase() (addr []byte, err error) {
// TODO: persist coinbase address on disk
return am.firstAddr()
}
func (am *Manager) firstAddr() ([]byte, error) {
addrs, err := am.keyStore.GetKeyAddresses()
if os.IsNotExist(err) {
return nil, ErrNoKeys
} else if err != nil {
return nil, err
}
if len(addrs) == 0 {
return nil, ErrNoKeys
}
return addrs[0], nil
}
func (am *Manager) DeleteAccount(address []byte, auth string) error {
return am.keyStore.DeleteKey(address, auth)
}
func (am *AccountManager) Sign(fromAccount *Account, toSign []byte) (signature []byte, err error) {
func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
am.mutex.RLock()
unlockedKey := am.unlockedKeys[string(fromAccount.Address)]
unlockedKey, found := am.unlocked[string(a.Address)]
am.mutex.RUnlock()
if unlockedKey.Address == nil {
if !found {
return nil, ErrLocked
}
signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey)
return signature, err
}
func (am *AccountManager) SignLocked(fromAccount *Account, keyAuth string, toSign []byte) (signature []byte, err error) {
key, err := am.keyStore.GetKey(fromAccount.Address, keyAuth)
// TimedUnlock unlocks the account with the given address.
// When timeout has passed, the account will be locked again.
func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error {
key, err := am.keyStore.GetKey(addr, keyAuth)
if err != nil {
return nil, err
return err
}
am.mutex.RLock()
am.unlockedKeys[string(fromAccount.Address)] = *key
am.mutex.RUnlock()
go unlockLater(am, fromAccount.Address)
signature, err = crypto.Sign(toSign, key.PrivateKey)
return signature, err
u := am.addUnlocked(addr, key)
go am.dropLater(addr, u, timeout)
return nil
}
func (am AccountManager) NewAccount(auth string) (*Account, error) {
// Unlock unlocks the account with the given address. The account
// stays unlocked until the program exits or until a TimedUnlock
// timeout (started after the call to Unlock) expires.
func (am *Manager) Unlock(addr []byte, keyAuth string) error {
key, err := am.keyStore.GetKey(addr, keyAuth)
if err != nil {
return err
}
am.addUnlocked(addr, key)
return nil
}
func (am *Manager) NewAccount(auth string) (Account, error) {
key, err := am.keyStore.GenerateNewKey(crand.Reader, auth)
if err != nil {
return nil, err
return Account{}, err
}
ua := &Account{
Address: key.Address,
}
return ua, err
return Account{Address: key.Address}, nil
}
func (am *AccountManager) Accounts() ([]Account, error) {
func (am *Manager) Accounts() ([]Account, error) {
addresses, err := am.keyStore.GetKeyAddresses()
if err != nil {
if os.IsNotExist(err) {
return nil, ErrNoKeys
} else if err != nil {
return nil, err
}
accounts := make([]Account, len(addresses))
for i, addr := range addresses {
accounts[i] = Account{
Address: addr,
@ -121,12 +164,47 @@ func (am *AccountManager) Accounts() ([]Account, error) {
return accounts, err
}
func unlockLater(am *AccountManager, addr []byte) {
select {
case <-time.After(time.Millisecond * am.unlockMilliseconds):
func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
u := &unlocked{Key: key, abort: make(chan struct{})}
am.mutex.Lock()
prev, found := am.unlocked[string(addr)]
if found {
// terminate dropLater for this key to avoid unexpected drops.
close(prev.abort)
// the key is zeroed here instead of in dropLater because
// there might not actually be a dropLater running for this
// key, i.e. when Unlock was used.
zeroKey(prev.PrivateKey)
}
am.unlocked[string(addr)] = u
am.mutex.Unlock()
return u
}
func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) {
t := time.NewTimer(timeout)
defer t.Stop()
select {
case <-u.abort:
// just quit
case <-t.C:
am.mutex.Lock()
// only drop if it's still the same key instance that dropLater
// was launched with. we can check that using pointer equality
// because the map stores a new pointer every time the key is
// unlocked.
if am.unlocked[string(addr)] == u {
zeroKey(u.PrivateKey)
delete(am.unlocked, string(addr))
}
am.mutex.Unlock()
}
}
// zeroKey zeroes a private key in memory.
func zeroKey(k *ecdsa.PrivateKey) {
b := k.D.Bits()
for i := range b {
b[i] = 0
}
am.mutex.RLock()
// TODO: how do we know the key is actually gone from memory?
delete(am.unlockedKeys, string(addr))
am.mutex.RUnlock()
}

View File

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

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 (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"math/big"
"path"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core"
types "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/rlp"
)
const (
ClientIdentifier = "Ethereum(G)"
Version = "0.8.6"
)
type Account struct {
Balance string
Code string
@ -78,6 +70,7 @@ type BlockHeader struct {
TransactionsTrie string
UncleHash string
}
type Tx struct {
Data string
GasLimit string
@ -103,108 +96,44 @@ type Test struct {
Pre map[string]Account
}
var (
Identifier string
KeyRing string
DiffTool bool
DiffType string
KeyStore string
StartRpc bool
StartWebSockets bool
RpcListenAddress string
RpcPort int
WsPort int
OutboundPort string
ShowGenesis bool
AddPeer string
MaxPeer int
GenAddr bool
BootNodes string
NodeKey *ecdsa.PrivateKey
NAT nat.Interface
SecretFile string
ExportDir string
NonInteractive bool
Datadir string
LogFile string
ConfigFile string
DebugFile string
LogLevel int
LogFormat string
Dump bool
DumpHash string
DumpNumber int
VmType int
ImportChain string
SHH bool
Dial bool
PrintVersion bool
MinerThreads int
)
// flags specific to cli client
var (
StartMining bool
StartJsConsole bool
InputFile string
)
func main() {
init_vars()
Init()
if len(TestFile) < 1 {
log.Fatal("Please specify test file")
}
blocks, err := loadBlocksFromTestFile(TestFile)
if err != nil {
panic(err)
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "%s <testfile>\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU())
logger.AddLogSystem(logger.NewStdLogSystem(os.Stderr, log.LstdFlags, logger.DebugDetailLevel))
defer func() { logger.Flush() }()
defer func() {
logger.Flush()
}()
if len(os.Args) < 2 {
utils.Fatalf("Please specify a test file as the first argument.")
}
blocks, err := loadBlocksFromTestFile(os.Args[1])
if err != nil {
utils.Fatalf("Could not load blocks: %v", err)
}
//utils.HandleInterrupt()
chain := memchain()
chain.ResetWithGenesisBlock(blocks[0])
if err = chain.InsertChain(types.Blocks{blocks[1]}); err != nil {
utils.Fatalf("Error: %v", err)
} else {
fmt.Println("PASS")
}
}
utils.InitConfig(VmType, ConfigFile, Datadir, "ethblocktest")
ethereum, err := eth.New(&eth.Config{
Name: p2p.MakeName(ClientIdentifier, Version),
KeyStore: KeyStore,
DataDir: Datadir,
LogFile: LogFile,
LogLevel: LogLevel,
LogFormat: LogFormat,
MaxPeers: MaxPeer,
Port: OutboundPort,
NAT: NAT,
KeyRing: KeyRing,
Shh: true,
Dial: Dial,
BootNodes: BootNodes,
NodeKey: NodeKey,
MinerThreads: MinerThreads,
})
utils.StartEthereumForTest(ethereum)
utils.StartRpc(ethereum, RpcListenAddress, RpcPort)
ethereum.ChainManager().ResetWithGenesisBlock(blocks[0])
// bph := ethereum.ChainManager().GetBlock(blocks[1].Header().ParentHash)
// fmt.Println("bph: ", bph)
//fmt.Println("b0: ", hex.EncodeToString(ethutil.Encode(blocks[0].RlpData())))
//fmt.Println("b0: ", hex.EncodeToString(blocks[0].Hash()))
//fmt.Println("b1: ", hex.EncodeToString(ethutil.Encode(blocks[1].RlpData())))
//fmt.Println("b1: ", hex.EncodeToString(blocks[1].Hash()))
go ethereum.ChainManager().InsertChain(types.Blocks{blocks[1]})
fmt.Println("OK! ")
ethereum.WaitForShutdown()
func memchain() *core.ChainManager {
blockdb, err := ethdb.NewMemDatabase()
if err != nil {
utils.Fatalf("Could not create in-memory database: %v", err)
}
statedb, err := ethdb.NewMemDatabase()
if err != nil {
utils.Fatalf("Could not create in-memory database: %v", err)
}
return core.NewChainManager(blockdb, statedb, new(event.TypeMux))
}
func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) {
@ -212,9 +141,8 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) {
if err != nil {
return
}
bt := *new(map[string]Test)
err = json.Unmarshal(fileContent, &bt)
if err != nil {
bt := make(map[string]Test)
if err = json.Unmarshal(fileContent, &bt); err != nil {
return
}
@ -280,49 +208,6 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) {
return
}
func init_vars() {
VmType = 0
Identifier = ""
KeyRing = ""
KeyStore = "db"
RpcListenAddress = "127.0.0.1"
RpcPort = 8545
WsPort = 40404
StartRpc = true
StartWebSockets = false
NonInteractive = false
GenAddr = false
SecretFile = ""
ExportDir = ""
LogFile = ""
timeStr := strconv.FormatInt(time.Now().UnixNano(), 10)
Datadir = path.Join(ethutil.DefaultDataDir(), timeStr)
ConfigFile = path.Join(ethutil.DefaultDataDir(), timeStr, "conf.ini")
DebugFile = ""
LogLevel = 5
LogFormat = "std"
DiffTool = false
DiffType = "all"
ShowGenesis = false
ImportChain = ""
Dump = false
DumpHash = ""
DumpNumber = -1
StartMining = false
StartJsConsole = false
PrintVersion = false
MinerThreads = runtime.NumCPU()
Dial = false
OutboundPort = "30303"
BootNodes = ""
MaxPeer = 1
}
func hex_decode(s string) (res []byte, err error) {
return hex.DecodeString(strings.TrimPrefix(s, "0x"))
}

View File

@ -22,11 +22,9 @@ import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"path"
"strings"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil"
@ -37,51 +35,123 @@ import (
"github.com/peterh/liner"
)
func execJsFile(ethereum *eth.Ethereum, filename string) {
file, err := os.Open(filename)
if err != nil {
utils.Fatalf("%v", err)
}
content, err := ioutil.ReadAll(file)
if err != nil {
utils.Fatalf("%v", err)
}
re := javascript.NewJSRE(xeth.New(ethereum, nil))
if _, err := re.Run(string(content)); err != nil {
utils.Fatalf("Javascript Error: %v", err)
}
type prompter interface {
AppendHistory(string)
Prompt(p string) (string, error)
PasswordPrompt(p string) (string, error)
}
type repl struct {
type dumbterm struct{ r *bufio.Reader }
func (r dumbterm) Prompt(p string) (string, error) {
fmt.Print(p)
return r.r.ReadString('\n')
}
func (r dumbterm) PasswordPrompt(p string) (string, error) {
fmt.Println("!! Unsupported terminal, password will echo.")
fmt.Print(p)
input, err := bufio.NewReader(os.Stdin).ReadString('\n')
fmt.Println()
return input, err
}
func (r dumbterm) AppendHistory(string) {}
type jsre struct {
re *javascript.JSRE
ethereum *eth.Ethereum
xeth *xeth.XEth
prompt string
lr *liner.State
ps1 string
atexit func()
prompter
}
func runREPL(ethereum *eth.Ethereum) {
xeth := xeth.New(ethereum, nil)
repl := &repl{
re: javascript.NewJSRE(xeth),
xeth: xeth,
ethereum: ethereum,
prompt: "> ",
}
repl.initStdFuncs()
func newJSRE(ethereum *eth.Ethereum) *jsre {
js := &jsre{ethereum: ethereum, ps1: "> "}
js.xeth = xeth.New(ethereum, js)
js.re = javascript.NewJSRE(js.xeth)
js.initStdFuncs()
if !liner.TerminalSupported() {
repl.dumbRead()
js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
} else {
lr := liner.NewLiner()
defer lr.Close()
js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
lr.SetCtrlCAborts(true)
repl.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
repl.read(lr)
repl.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
js.prompter = lr
js.atexit = func() {
js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
lr.Close()
}
}
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 {
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 {
break
}
if input == "" {
continue
}
str += input + "\n"
self.setIndent()
if indentCount <= 0 {
if input == "exit" {
break
}
hist := str[:len(str)-1]
self.AppendHistory(hist)
self.parseInput(str)
str = ""
}
}
if self.atexit != nil {
self.atexit()
}
}
func (self *jsre) withHistory(op func(*os.File)) {
hist, err := os.OpenFile(path.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil {
fmt.Printf("unable to open history file: %v\n", err)
@ -91,7 +161,7 @@ func (self *repl) withHistory(op func(*os.File)) {
hist.Close()
}
func (self *repl) parseInput(code string) {
func (self *jsre) parseInput(code string) {
defer func() {
if r := recover(); r != nil {
fmt.Println("[native] error", r)
@ -108,79 +178,21 @@ func (self *repl) parseInput(code string) {
var indentCount = 0
var str = ""
func (self *repl) setIndent() {
func (self *jsre) setIndent() {
open := strings.Count(str, "{")
open += strings.Count(str, "(")
closed := strings.Count(str, "}")
closed += strings.Count(str, ")")
indentCount = open - closed
if indentCount <= 0 {
self.prompt = "> "
self.ps1 = "> "
} else {
self.prompt = strings.Join(make([]string, indentCount*2), "..")
self.prompt += " "
self.ps1 = strings.Join(make([]string, indentCount*2), "..")
self.ps1 += " "
}
}
func (self *repl) read(lr *liner.State) {
for {
input, err := lr.Prompt(self.prompt)
if err != nil {
return
}
if input == "" {
continue
}
str += input + "\n"
self.setIndent()
if indentCount <= 0 {
if input == "exit" {
return
}
hist := str[:len(str)-1]
lr.AppendHistory(hist)
self.parseInput(str)
str = ""
}
}
}
func (self *repl) dumbRead() {
fmt.Println("Unsupported terminal, line editing will not work.")
// process lines
readDone := make(chan struct{})
go func() {
r := bufio.NewReader(os.Stdin)
loop:
for {
fmt.Print(self.prompt)
line, err := r.ReadString('\n')
switch {
case err != nil || line == "exit":
break loop
case line == "":
continue
default:
self.parseInput(line + "\n")
}
}
close(readDone)
}()
// wait for Ctrl-C
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill)
defer signal.Stop(sigc)
select {
case <-readDone:
case <-sigc:
os.Stdin.Close() // terminate read
}
}
func (self *repl) printValue(v interface{}) {
func (self *jsre) printValue(v interface{}) {
method, _ := self.re.Vm.Get("prettyPrint")
v, err := self.re.Vm.ToValue(v)
if err == nil {
@ -191,7 +203,7 @@ func (self *repl) printValue(v interface{}) {
}
}
func (self *repl) initStdFuncs() {
func (self *jsre) initStdFuncs() {
t, _ := self.re.Vm.Get("eth")
eth := t.Object()
eth.Set("connect", self.connect)
@ -205,7 +217,7 @@ func (self *repl) initStdFuncs() {
* The following methods are natively implemented javascript functions.
*/
func (self *repl) dump(call otto.FunctionCall) otto.Value {
func (self *jsre) dump(call otto.FunctionCall) otto.Value {
var block *types.Block
if len(call.ArgumentList) > 0 {
@ -236,17 +248,17 @@ func (self *repl) dump(call otto.FunctionCall) otto.Value {
return v
}
func (self *repl) stopMining(call otto.FunctionCall) otto.Value {
func (self *jsre) stopMining(call otto.FunctionCall) otto.Value {
self.xeth.Miner().Stop()
return otto.TrueValue()
}
func (self *repl) startMining(call otto.FunctionCall) otto.Value {
func (self *jsre) startMining(call otto.FunctionCall) otto.Value {
self.xeth.Miner().Start()
return otto.TrueValue()
}
func (self *repl) connect(call otto.FunctionCall) otto.Value {
func (self *jsre) connect(call otto.FunctionCall) otto.Value {
nodeURL, err := call.Argument(0).ToString()
if err != nil {
return otto.FalseValue()
@ -257,7 +269,7 @@ func (self *repl) connect(call otto.FunctionCall) otto.Value {
return otto.TrueValue()
}
func (self *repl) export(call otto.FunctionCall) otto.Value {
func (self *jsre) export(call otto.FunctionCall) otto.Value {
if len(call.ArgumentList) == 0 {
fmt.Println("err: require file name")
return otto.FalseValue()

View File

@ -21,19 +21,23 @@
package main
import (
"bufio"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state"
"github.com/peterh/liner"
)
const (
@ -43,12 +47,10 @@ const (
var (
clilogger = logger.NewLogger("CLI")
app = cli.NewApp()
app = utils.NewApp(Version, "the go-ethereum command line interface")
)
func init() {
app.Version = Version
app.Usage = "the go-ethereum command-line client"
app.Action = run
app.HideVersion = true // we have a command to print the version
app.Commands = []cli.Command{
@ -60,6 +62,23 @@ func init() {
The output of this command is supposed to be machine-readable.
`,
},
{
Action: accountList,
Name: "account",
Usage: "manage accounts",
Subcommands: []cli.Command{
{
Action: accountList,
Name: "list",
Usage: "print account addresses",
},
{
Action: accountCreate,
Name: "new",
Usage: "create a new account",
},
},
},
{
Action: dump,
Name: "dump",
@ -93,13 +112,10 @@ runtime will execute the file and exit.
Usage: `export blockchain into file`,
},
}
app.Author = ""
app.Email = ""
app.Flags = []cli.Flag{
utils.UnlockedAccountFlag,
utils.BootnodesFlag,
utils.DataDirFlag,
utils.KeyRingFlag,
utils.KeyStoreFlag,
utils.ListenPortFlag,
utils.LogFileFlag,
utils.LogFormatFlag,
@ -140,38 +156,100 @@ func main() {
func run(ctx *cli.Context) {
fmt.Printf("Welcome to the FRONTIER\n")
utils.HandleInterrupt()
eth := utils.GetEthereum(ClientIdentifier, Version, ctx)
eth, err := utils.GetEthereum(ClientIdentifier, Version, ctx)
if err == accounts.ErrNoKeys {
utils.Fatalf(`No accounts configured.
Please run 'ethereum account new' to create a new account.`)
} else if err != nil {
utils.Fatalf("%v", err)
}
startEth(ctx, eth)
// this blocks the thread
eth.WaitForShutdown()
}
func runjs(ctx *cli.Context) {
eth := utils.GetEthereum(ClientIdentifier, Version, ctx)
startEth(ctx, eth)
if len(ctx.Args()) == 0 {
runREPL(eth)
eth.Stop()
eth.WaitForShutdown()
} else if len(ctx.Args()) == 1 {
execJsFile(eth, ctx.Args()[0])
} else {
utils.Fatalf("This command can handle at most one argument.")
eth, err := utils.GetEthereum(ClientIdentifier, Version, ctx)
if err == accounts.ErrNoKeys {
utils.Fatalf(`No accounts configured.
Please run 'ethereum account new' to create a new account.`)
} else if err != nil {
utils.Fatalf("%v", err)
}
startEth(ctx, eth)
repl := newJSRE(eth)
if len(ctx.Args()) == 0 {
repl.interactive()
} else {
for _, file := range ctx.Args() {
repl.exec(file)
}
}
eth.Stop()
eth.WaitForShutdown()
}
func startEth(ctx *cli.Context, eth *eth.Ethereum) {
utils.StartEthereum(eth)
// Load startup keys. XXX we are going to need a different format
account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
if len(account) > 0 {
split := strings.Split(account, ":")
if len(split) != 2 {
utils.Fatalf("Illegal 'unlock' format (address:password)")
}
am := utils.GetAccountManager(ctx)
// Attempt to unlock the account
err := am.Unlock(ethutil.Hex2Bytes(split[0]), split[1])
if err != nil {
utils.Fatalf("Unlock account failed '%v'", err)
}
}
// Start auxiliary services if enabled.
if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
addr := ctx.GlobalString(utils.RPCListenAddrFlag.Name)
port := ctx.GlobalInt(utils.RPCPortFlag.Name)
utils.StartRpc(eth, addr, port)
utils.StartRPC(eth, ctx)
}
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
eth.Miner().Start()
}
}
func accountList(ctx *cli.Context) {
am := utils.GetAccountManager(ctx)
accts, err := am.Accounts()
if err != nil {
utils.Fatalf("Could not list accounts: %v", err)
}
for _, acct := range accts {
fmt.Printf("Address: %#x\n", acct)
}
}
func accountCreate(ctx *cli.Context) {
am := utils.GetAccountManager(ctx)
fmt.Println("The new account will be encrypted with a passphrase.")
fmt.Println("Please enter a passphrase now.")
auth, err := readPassword("Passphrase: ", true)
if err != nil {
utils.Fatalf("%v", err)
}
confirm, err := readPassword("Repeat Passphrase: ", false)
if err != nil {
utils.Fatalf("%v", err)
}
if auth != confirm {
utils.Fatalf("Passphrases did not match.")
}
acct, err := am.NewAccount(auth)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
}
fmt.Printf("Address: %#x\n", acct.Address)
}
func importchain(ctx *cli.Context) {
if len(ctx.Args()) != 1 {
utils.Fatalf("This command requires an argument.")
@ -221,12 +299,6 @@ func dump(ctx *cli.Context) {
}
}
// hashish returns true for strings that look like hashes.
func hashish(x string) bool {
_, err := strconv.Atoi(x)
return err != nil
}
func version(c *cli.Context) {
fmt.Printf(`%v
Version: %v
@ -238,3 +310,24 @@ GOPATH=%s
GOROOT=%s
`, ClientIdentifier, Version, eth.ProtocolVersion, eth.NetworkId, runtime.Version(), runtime.GOOS, 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)))
ethutil.ReadConfig("/tmp/evmtest", "/tmp/evm", "")
db, _ := ethdb.NewMemDatabase()
statedb := state.New(nil, db)
sender := statedb.NewStateObject([]byte("sender"))

View File

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

View File

@ -54,7 +54,6 @@ Rectangle {
height: 200
anchors {
left: parent.left
right: logLevelSlider.left
bottom: parent.bottom
top: parent.top
}
@ -107,46 +106,6 @@ Rectangle {
}
}
}
/*
TableView {
id: logView
headerVisible: false
anchors {
right: logLevelSlider.left
left: parent.left
bottom: parent.bottom
top: parent.top
}
TableViewColumn{ role: "description" ; title: "log" }
model: logModel
}
*/
Slider {
id: logLevelSlider
value: gui.getLogLevelInt()
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
rightMargin: 5
leftMargin: 5
topMargin: 5
bottomMargin: 5
}
orientation: Qt.Vertical
maximumValue: 5
stepSize: 1
onValueChanged: {
gui.setLogLevel(value)
}
}
}
property var logModel: ListModel {

View File

@ -28,7 +28,6 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state"
)
@ -37,19 +36,7 @@ type plugin struct {
Path string `json:"path"`
}
// LogPrint writes to the GUI log.
func (gui *Gui) LogPrint(level logger.LogLevel, msg string) {
/*
str := strings.TrimRight(s, "\n")
lines := strings.Split(str, "\n")
view := gui.getObjectByName("infoView")
for _, line := range lines {
view.Call("addLog", line)
}
*/
}
func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (string, error) {
func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (string, error) {
var data string
if len(recipient) == 0 {
code, err := ethutil.Compile(d, false)
@ -61,18 +48,7 @@ func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (string, err
data = ethutil.Bytes2Hex(utils.FormatTransactionData(d))
}
return gui.xeth.Transact(recipient, value, gas, gasPrice, data)
}
// functions that allow Gui to implement interface guilogger.LogSystem
func (gui *Gui) SetLogLevel(level logger.LogLevel) {
gui.logLevel = level
gui.eth.Logger().SetLogLevel(level)
gui.config.Save("loglevel", level)
}
func (gui *Gui) GetLogLevel() logger.LogLevel {
return gui.logLevel
return gui.xeth.Transact(from, recipient, value, gas, gasPrice, data)
}
func (self *Gui) AddPlugin(pluginPath string) {
@ -89,11 +65,6 @@ func (self *Gui) RemovePlugin(pluginPath string) {
ethutil.WriteFile(self.eth.DataDir+"/plugins.json", json)
}
// this extra function needed to give int typecast value to gui widget
// that sets initial loglevel to default
func (gui *Gui) GetLogLevelInt() int {
return int(gui.logLevel)
}
func (self *Gui) DumpState(hash, path string) {
var stateDump []byte

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 (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
@ -70,20 +69,18 @@ type Gui struct {
txDb *ethdb.LDBDatabase
logLevel logger.LogLevel
open bool
open bool
xeth *xeth.XEth
Session string
config *ethutil.ConfigManager
plugins map[string]plugin
}
// Create GUI, but doesn't start it
func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session string, logLevel int) *Gui {
db, err := ethdb.NewLDBDatabase("tx_database")
func NewWindow(ethereum *eth.Ethereum) *Gui {
db, err := ethdb.NewLDBDatabase(path.Join(ethereum.DataDir, "tx_database"))
if err != nil {
panic(err)
}
@ -92,10 +89,7 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session st
gui := &Gui{eth: ethereum,
txDb: db,
xeth: xeth,
logLevel: logger.LogLevel(logLevel),
Session: session,
open: false,
config: config,
plugins: make(map[string]plugin),
serviceEvents: make(chan ServEv, 1),
}
@ -142,18 +136,12 @@ func (gui *Gui) Start(assetPath string) {
gui.open = true
win.Show()
// only add the gui guilogger after window is shown otherwise slider wont be shown
logger.AddLogSystem(gui)
win.Wait()
// need to silence gui guilogger after window closed otherwise logsystem hangs (but do not save loglevel)
gui.logLevel = logger.Silence
gui.open = false
}
func (gui *Gui) Stop() {
if gui.open {
gui.logLevel = logger.Silence
gui.open = false
gui.win.Hide()
}
@ -172,7 +160,11 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
return gui.win, nil
}
func (gui *Gui) ImportKey(filePath string) {
func (gui *Gui) GenerateKey() {
_, err := gui.eth.AccountManager().NewAccount("hurr")
if err != nil {
// TODO: UI feedback?
}
}
func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
@ -191,31 +183,11 @@ func (gui *Gui) createWindow(comp qml.Object) *qml.Window {
return gui.win
}
func (gui *Gui) ImportAndSetPrivKey(secret string) bool {
err := gui.eth.KeyManager().InitFromString(gui.Session, 0, secret)
if err != nil {
guilogger.Errorln("unable to import: ", err)
return false
}
guilogger.Errorln("successfully imported: ", err)
return true
}
func (gui *Gui) CreateAndSetPrivKey() (string, string, string, string) {
err := gui.eth.KeyManager().Init(gui.Session, 0, true)
if err != nil {
guilogger.Errorln("unable to create key: ", err)
return "", "", "", ""
}
return gui.eth.KeyManager().KeyPair().AsStrings()
}
func (gui *Gui) setInitialChain(ancientBlocks bool) {
sBlk := gui.eth.ChainManager().LastBlockHash()
blk := gui.eth.ChainManager().GetBlock(sBlk)
for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) {
sBlk = blk.ParentHash()
gui.processBlock(blk, true)
}
}
@ -259,10 +231,8 @@ func (self *Gui) loadMergedMiningOptions() {
}
func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
addr := gui.address()
var inout string
if bytes.Compare(tx.From(), addr) == 0 {
if gui.eth.AccountManager().HasAccount(tx.From()) {
inout = "send"
} else {
inout = "recv"
@ -480,14 +450,6 @@ func (gui *Gui) setPeerInfo() {
}
}
func (gui *Gui) privateKey() string {
return ethutil.Bytes2Hex(gui.eth.KeyManager().PrivateKey())
}
func (gui *Gui) address() []byte {
return gui.eth.KeyManager().Address()
}
/*
func LoadExtension(path string) (uintptr, error) {
lib, err := ffi.NewLibrary(path)

View File

@ -26,10 +26,10 @@ import (
"runtime"
"time"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/ui/qt/webengine"
"github.com/obscuren/qml"
)
@ -39,56 +39,32 @@ const (
Version = "0.9.0"
)
var ethereum *eth.Ethereum
var mainlogger = logger.NewLogger("MAIN")
func run() error {
webengine.Initialize()
// precedence: code-internal flag default < config file < environment variables < command line
Init() // parsing command line
tstart := time.Now()
config := utils.InitConfig(VmType, ConfigFile, Datadir, "ETH")
ethereum, err := eth.New(&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)
var (
app = utils.NewApp(Version, "the ether browser")
assetPathFlag = cli.StringFlag{
Name: "asset_path",
Usage: "absolute path to GUI assets directory",
Value: ethutil.DefaultAssetPath(),
}
utils.KeyTasks(ethereum.KeyManager(), KeyRing, GenAddr, SecretFile, ExportDir, NonInteractive)
)
if StartRpc {
utils.StartRpc(ethereum, RpcListenAddress, RpcPort)
func init() {
app.Action = run
app.Flags = []cli.Flag{
assetPathFlag,
utils.BootnodesFlag,
utils.DataDirFlag,
utils.ListenPortFlag,
utils.LogFileFlag,
utils.LogLevelFlag,
utils.MaxPeersFlag,
utils.MinerThreadsFlag,
utils.NATFlag,
utils.NodeKeyFileFlag,
utils.RPCListenAddrFlag,
utils.RPCPortFlag,
}
gui := NewWindow(ethereum, config, KeyRing, LogLevel)
utils.RegisterInterrupt(func(os.Signal) {
gui.Stop()
})
go utils.StartEthereum(ethereum)
fmt.Println("ETH stack took", time.Since(tstart))
// gui blocks the main thread
gui.Start(AssetPath)
return nil
}
func main() {
@ -97,15 +73,16 @@ func main() {
// This is a bit of a cheat, but ey!
os.Setenv("QTWEBKIT_INSPECTOR_SERVER", "127.0.0.1:99999")
qml.Run(run)
var interrupted = false
utils.RegisterInterrupt(func(os.Signal) {
interrupted = true
})
utils.HandleInterrupt()
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, "Error: ", err)
}
// we need to run the interrupt callbacks in case gui is closed
// this skips if we got here by actual interrupt stopping the GUI
if !interrupted {
@ -113,3 +90,26 @@ func main() {
}
logger.Flush()
}
func run(ctx *cli.Context) {
tstart := time.Now()
// TODO: show qml popup instead of exiting if initialization fails.
ethereum, err := utils.GetEthereum(ClientIdentifier, Version, ctx)
if err != nil {
utils.Fatalf("%v", err)
}
utils.StartRPC(ethereum, ctx)
go utils.StartEthereum(ethereum)
fmt.Println("initializing eth stack took", time.Since(tstart))
// 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

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

View File

@ -29,13 +29,10 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/rlp"
rpchttp "github.com/ethereum/go-ethereum/rpc/http"
"github.com/ethereum/go-ethereum/xeth"
)
var clilogger = logger.NewLogger("CLI")
@ -97,18 +94,10 @@ func initDataDir(Datadir string) {
}
}
func InitConfig(vmType int, ConfigFile string, Datadir string, EnvPrefix string) *ethutil.ConfigManager {
initDataDir(Datadir)
cfg := ethutil.ReadConfig(ConfigFile, Datadir, EnvPrefix)
cfg.VmType = vmType
return cfg
}
func exit(err error) {
status := 0
if err != nil {
fmt.Fprintln(os.Stderr, "Fatal: ", err)
fmt.Fprintln(os.Stderr, "Fatal:", err)
status = 1
}
logger.Flush()
@ -142,47 +131,6 @@ func StartEthereumForTest(ethereum *eth.Ethereum) {
})
}
func KeyTasks(keyManager *crypto.KeyManager, KeyRing string, GenAddr bool, SecretFile string, ExportDir string, NonInteractive bool) {
var err error
switch {
case GenAddr:
if NonInteractive || confirm("This action overwrites your old private key.") {
err = keyManager.Init(KeyRing, 0, true)
}
exit(err)
case len(SecretFile) > 0:
SecretFile = ethutil.ExpandHomePath(SecretFile)
if NonInteractive || confirm("This action overwrites your old private key.") {
err = keyManager.InitFromSecretsFile(KeyRing, 0, SecretFile)
}
exit(err)
case len(ExportDir) > 0:
err = keyManager.Init(KeyRing, 0, false)
if err == nil {
err = keyManager.Export(ExportDir)
}
exit(err)
default:
// Creates a keypair if none exists
err = keyManager.Init(KeyRing, 0, false)
if err != nil {
exit(err)
}
}
clilogger.Infof("Main address %x\n", keyManager.Address())
}
func StartRpc(ethereum *eth.Ethereum, RpcListenAddress string, RpcPort int) {
var err error
ethereum.RpcServer, err = rpchttp.NewRpcHttpServer(xeth.New(ethereum, nil), RpcListenAddress, RpcPort)
if err != nil {
clilogger.Errorf("Could not start RPC interface (port %v): %v", RpcPort, err)
} else {
go ethereum.RpcServer.Start()
}
}
func FormatTransactionData(data string) []byte {
d := ethutil.StringToByteFunc(data, func(s string) (ret []byte) {
slice := regexp.MustCompile("\\n|\\s").Split(s, 1000000000)

View File

@ -2,10 +2,15 @@ package utils
import (
"crypto/ecdsa"
"fmt"
"net"
"net/http"
"os"
"path"
"runtime"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
@ -15,8 +20,48 @@ import (
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/xeth"
)
func init() {
cli.AppHelpTemplate = `{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
VERSION:
{{.Version}}
COMMANDS:
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
{{end}}{{if .Flags}}
GLOBAL OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}
`
cli.CommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...]
{{if .Description}}{{.Description}}
{{end}}{{if .Subcommands}}
SUBCOMMANDS:
{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
{{end}}{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}
`
}
// NewApp creates an app with sane defaults.
func NewApp(version, usage string) *cli.App {
app := cli.NewApp()
app.Name = path.Base(os.Args[0])
app.Author = ""
app.Authors = nil
app.Email = ""
app.Version = version
app.Usage = usage
return app
}
// These are all the command line flags we support.
// If you add to this list, please remember to include the
// flag in the appropriate command definition.
@ -32,20 +77,14 @@ var (
Usage: "Virtual Machine type: 0 is standard VM, 1 is debug VM",
}
*/
UnlockedAccountFlag = cli.StringFlag{
Name: "unlock",
Usage: "Unlock a given account untill this programs exits (address:password)",
}
VMDebugFlag = cli.BoolFlag{
Name: "vmdebug",
Usage: "Virtual Machine debug output",
}
KeyRingFlag = cli.StringFlag{
Name: "keyring",
Usage: "Name of keyring to be used",
Value: "",
}
KeyStoreFlag = cli.StringFlag{
Name: "keystore",
Usage: `Where to store keyrings: "db" or "file"`,
Value: "db",
}
DataDirFlag = cli.StringFlag{
Name: "datadir",
Usage: "Data directory to be used",
@ -149,30 +188,24 @@ func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) {
return key
}
func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum {
ethereum, err := eth.New(&eth.Config{
Name: p2p.MakeName(clientID, version),
KeyStore: ctx.GlobalString(KeyStoreFlag.Name),
DataDir: ctx.GlobalString(DataDirFlag.Name),
LogFile: ctx.GlobalString(LogFileFlag.Name),
LogLevel: ctx.GlobalInt(LogLevelFlag.Name),
LogFormat: ctx.GlobalString(LogFormatFlag.Name),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
VmDebug: ctx.GlobalBool(VMDebugFlag.Name),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
Port: ctx.GlobalString(ListenPortFlag.Name),
NAT: GetNAT(ctx),
NodeKey: GetNodeKey(ctx),
KeyRing: ctx.GlobalString(KeyRingFlag.Name),
Shh: true,
Dial: true,
BootNodes: ctx.GlobalString(BootnodesFlag.Name),
func GetEthereum(clientID, version string, ctx *cli.Context) (*eth.Ethereum, error) {
return eth.New(&eth.Config{
Name: p2p.MakeName(clientID, version),
DataDir: ctx.GlobalString(DataDirFlag.Name),
LogFile: ctx.GlobalString(LogFileFlag.Name),
LogLevel: ctx.GlobalInt(LogLevelFlag.Name),
LogFormat: ctx.GlobalString(LogFormatFlag.Name),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
AccountManager: GetAccountManager(ctx),
VmDebug: ctx.GlobalBool(VMDebugFlag.Name),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
Port: ctx.GlobalString(ListenPortFlag.Name),
NAT: GetNAT(ctx),
NodeKey: GetNodeKey(ctx),
Shh: true,
Dial: true,
BootNodes: ctx.GlobalString(BootnodesFlag.Name),
})
if err != nil {
exit(err)
}
return ethereum
}
func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.Database) {
@ -188,3 +221,27 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.D
}
return core.NewChainManager(blockDb, stateDb, new(event.TypeMux)), blockDb, stateDb
}
// Global account manager
var km *accounts.Manager
func GetAccountManager(ctx *cli.Context) *accounts.Manager {
dataDir := ctx.GlobalString(DataDirFlag.Name)
if km == nil {
ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys"))
km = accounts.NewManager(ks)
}
return km
}
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/ethdb"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rlp"
)
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
ethutil.ReadConfig("/tmp/ethtest", "/tmp/ethtest", "ETH")
}
// Test fork of length N starting from block i

View File

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

View File

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

View File

@ -45,8 +45,6 @@ type StateTransition struct {
}
type Message interface {
Hash() []byte
From() []byte
To() []byte
@ -153,7 +151,7 @@ func (self *StateTransition) preCheck() (err error) {
}
func (self *StateTransition) TransitionState() (ret []byte, err error) {
statelogger.Debugf("(~) %x\n", self.msg.Hash())
// statelogger.Debugf("(~) %x\n", self.msg.Hash())
// XXX Transactions after this point are considered valid.
if err = self.preCheck(); err != nil {

View File

@ -129,6 +129,7 @@ func (tx *Transaction) sender() []byte {
return crypto.Sha3(pubkey[1:])[12:]
}
// TODO: deprecate after new accounts & key stores are integrated
func (tx *Transaction) Sign(privk []byte) error {
sig := tx.Signature(privk)
@ -140,6 +141,13 @@ func (tx *Transaction) Sign(privk []byte) error {
return nil
}
func (tx *Transaction) SetSignatureValues(sig []byte) error {
tx.R = sig[:32]
tx.S = sig[32:64]
tx.V = uint64(sig[64] + 27)
return nil
}
func (tx *Transaction) SignECDSA(key *ecdsa.PrivateKey) error {
return tx.Sign(crypto.FromECDSA(key))
}

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,9 @@ package rpc
import (
"encoding/json"
"fmt"
"math/big"
"path"
"strings"
"sync"
"time"
@ -46,8 +48,8 @@ type EthereumApi struct {
// defaultBlockAge int64
}
func NewEthereumApi(eth *xeth.XEth) *EthereumApi {
db, _ := ethdb.NewLDBDatabase("dapps")
func NewEthereumApi(eth *xeth.XEth, dataDir string) *EthereumApi {
db, _ := ethdb.NewLDBDatabase(path.Join(dataDir, "dapps"))
api := &EthereumApi{
eth: eth,
mux: eth.Backend().EventMux(),
@ -232,15 +234,7 @@ func (self *EthereumApi) AllLogs(args *FilterOptions, reply *interface{}) error
return nil
}
func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error {
if args.Gas == ethutil.Big0 {
args.Gas = defaultGas
}
if args.GasPrice == ethutil.Big0 {
args.GasPrice = defaultGasPrice
}
func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) (err error) {
// TODO if no_private_key then
//if _, exists := p.register[args.From]; exists {
// p.register[args.From] = append(p.register[args.From], args)
@ -262,18 +256,28 @@ func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error {
p.register[ags.From] = append(p.register[args.From], args)
}
*/
result, err := p.xeth().Transact( /* TODO specify account */ args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
// TODO: align default values to have the same type, e.g. not depend on
// ethutil.Value conversions later on
fmt.Println("gas", args.Gas)
if args.Gas.Cmp(big.NewInt(0)) == 0 {
args.Gas = defaultGas
}
if args.GasPrice.Cmp(big.NewInt(0)) == 0 {
args.GasPrice = defaultGasPrice
}
*reply, err = p.xeth().Transact(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
if err != nil {
fmt.Println("err:", err)
return err
}
*reply = result
//}
return nil
}
func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error {
result, err := p.xeth().Call( /* TODO specify account */ args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
result, err := p.xeth().Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
if err != nil {
return err
}

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
got := string(s.state.Dump())
want := `{
"root": "4e3a59299745ba6752247c8b91d0f716dac9ec235861c91f5ac1894a361d87ba",
"root": "6e277ae8357d013e50f74eedb66a991f6922f93ae03714de58b3d0c5e9eee53f",
"accounts": {
"0000000000000000000000000000000000000001": {
"1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d": {
"balance": "22",
"nonce": 0,
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"storage": {}
},
"0000000000000000000000000000000000000102": {
"a17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1": {
"balance": "0",
"nonce": 0,
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
@ -57,7 +57,6 @@ func (s *StateSuite) TestDump(c *checker.C) {
}
func (s *StateSuite) SetUpTest(c *checker.C) {
ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "")
db, _ := ethdb.NewMemDatabase()
s.state = New(nil, db)
}

View File

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

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

@ -38,11 +38,11 @@ func (self *peer) init() error {
func (self *peer) start() {
go self.update()
self.peer.Infoln("whisper started")
self.peer.Debugln("whisper started")
}
func (self *peer) stop() {
self.peer.Infoln("whisper stopped")
self.peer.Debugln("whisper stopped")
close(self.quit)
}

View File

@ -1,14 +1,13 @@
// eXtended ETHereum
package xeth
/*
* eXtended ETHereum
*/
import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@ -18,7 +17,6 @@ import (
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/ui"
"github.com/ethereum/go-ethereum/whisper"
)
@ -28,11 +26,11 @@ var pipelogger = logger.NewLogger("XETH")
type Backend interface {
BlockProcessor() *core.BlockProcessor
ChainManager() *core.ChainManager
AccountManager() *accounts.Manager
TxPool() *core.TxPool
PeerCount() int
IsListening() bool
Peers() []*p2p.Peer
KeyManager() *crypto.KeyManager
BlockDb() ethutil.Database
StateDb() ethutil.Database
EventMux() *event.TypeMux
@ -40,37 +38,62 @@ type Backend interface {
Miner() *miner.Miner
}
// Frontend should be implemented by users of XEth. Its methods are
// called whenever XEth makes a decision that requires user input.
type Frontend interface {
// UnlockAccount is called when a transaction needs to be signed
// but the key corresponding to the transaction's sender is
// locked.
//
// It should unlock the account with the given address and return
// true if unlocking succeeded.
UnlockAccount(address []byte) bool
// This is called for all transactions inititated through
// Transact. It should prompt the user to confirm the transaction
// and return true if the transaction was acknowledged.
//
// ConfirmTransaction is not used for Call transactions
// because they cannot change any state.
ConfirmTransaction(tx *types.Transaction) bool
}
type XEth struct {
eth Backend
blockProcessor *core.BlockProcessor
chainManager *core.ChainManager
accountManager *accounts.Manager
state *State
whisper *Whisper
miner *miner.Miner
frontend ui.Interface
frontend Frontend
}
type TmpFrontend struct{}
// dummyFrontend is a non-interactive frontend that allows all
// transactions but cannot not unlock any keys.
type dummyFrontend struct{}
func (TmpFrontend) UnlockAccount([]byte) bool { panic("UNLOCK ACCOUNT") }
func (TmpFrontend) ConfirmTransaction(*types.Transaction) bool { panic("CONFIRM TRANSACTION") }
func (dummyFrontend) UnlockAccount([]byte) bool { return false }
func (dummyFrontend) ConfirmTransaction(*types.Transaction) bool { return true }
func New(eth Backend, frontend ui.Interface) *XEth {
// New creates an XEth that uses the given frontend.
// If a nil Frontend is provided, a default frontend which
// confirms all transactions will be used.
func New(eth Backend, frontend Frontend) *XEth {
xeth := &XEth{
eth: eth,
blockProcessor: eth.BlockProcessor(),
chainManager: eth.ChainManager(),
accountManager: eth.AccountManager(),
whisper: NewWhisper(eth.Whisper()),
miner: eth.Miner(),
frontend: frontend,
}
if frontend == nil {
xeth.frontend = TmpFrontend{}
xeth.frontend = dummyFrontend{}
}
xeth.state = NewState(xeth, xeth.chainManager.TransState())
return xeth
}
@ -135,7 +158,13 @@ func (self *XEth) Block(v interface{}) *Block {
}
func (self *XEth) Accounts() []string {
return []string{toHex(self.eth.KeyManager().Address())}
// TODO: check err?
accounts, _ := self.eth.AccountManager().Accounts()
accountAddresses := make([]string, len(accounts))
for i, ac := range accounts {
accountAddresses[i] = toHex(ac.Address)
}
return accountAddresses
}
func (self *XEth) PeerCount() int {
@ -162,7 +191,8 @@ func (self *XEth) IsListening() bool {
}
func (self *XEth) Coinbase() string {
return toHex(self.eth.KeyManager().Address())
cb, _ := self.eth.AccountManager().Coinbase()
return toHex(cb)
}
func (self *XEth) NumberToHuman(balance string) string {
@ -263,7 +293,7 @@ func (self *XEth) PushTx(encodedTx string) (string, error) {
return toHex(tx.Hash()), nil
}
func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) {
func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) {
if len(gasStr) == 0 {
gasStr = "100000"
}
@ -271,41 +301,34 @@ func (self *XEth) Call(toStr, valueStr, gasStr, gasPriceStr, dataStr string) (st
gasPriceStr = "1"
}
var (
statedb = self.State().State() //self.chainManager.TransState()
key = self.eth.KeyManager().KeyPair()
from = statedb.GetOrNewStateObject(key.Address())
block = self.chainManager.CurrentBlock()
to = statedb.GetOrNewStateObject(fromHex(toStr))
data = fromHex(dataStr)
gas = ethutil.Big(gasStr)
price = ethutil.Big(gasPriceStr)
value = ethutil.Big(valueStr)
)
msg := types.NewTransactionMessage(fromHex(toStr), value, gas, price, data)
msg.Sign(key.PrivateKey)
statedb := self.State().State() //self.chainManager.TransState()
msg := callmsg{
from: statedb.GetOrNewStateObject(fromHex(fromStr)),
to: fromHex(toStr),
gas: ethutil.Big(gasStr),
gasPrice: ethutil.Big(gasPriceStr),
value: ethutil.Big(valueStr),
data: fromHex(dataStr),
}
block := self.chainManager.CurrentBlock()
vmenv := core.NewEnv(statedb, self.chainManager, msg, block)
res, err := vmenv.Call(from, to.Address(), data, gas, price, value)
if err != nil {
return "", err
}
return toHex(res), nil
res, err := vmenv.Call(msg.from, msg.to, msg.data, msg.gas, msg.gasPrice, msg.value)
return toHex(res), err
}
func (self *XEth) Transact(toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) {
func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) {
var (
from []byte
to []byte
value = ethutil.NewValue(valueStr)
gas = ethutil.NewValue(gasStr)
price = ethutil.NewValue(gasPriceStr)
data []byte
key = self.eth.KeyManager().KeyPair()
contractCreation bool
)
from = fromHex(fromStr)
data = fromHex(codeStr)
to = fromHex(toStr)
if len(to) == 0 {
@ -319,25 +342,61 @@ func (self *XEth) Transact(toStr, valueStr, gasStr, gasPriceStr, codeStr string)
tx = types.NewTransactionMessage(to, value.BigInt(), gas.BigInt(), price.BigInt(), data)
}
var err error
state := self.eth.ChainManager().TxState()
if balance := state.GetBalance(key.Address()); balance.Cmp(tx.Value()) < 0 {
return "", fmt.Errorf("insufficient balance. balance=%v tx=%v", balance, tx.Value())
}
nonce := state.GetNonce(key.Address())
state := self.chainManager.TxState()
nonce := state.GetNonce(from)
tx.SetNonce(nonce)
tx.Sign(key.PrivateKey)
err = self.eth.TxPool().Add(tx)
if err != nil {
if err := self.sign(tx, from, false); err != nil {
return "", err
}
state.SetNonce(key.Address(), nonce+1)
if err := self.eth.TxPool().Add(tx); err != nil {
return "", err
}
state.SetNonce(from, nonce+1)
if contractCreation {
addr := core.AddressFromMessage(tx)
pipelogger.Infof("Contract addr %x\n", addr)
}
if types.IsContractAddr(to) {
return toHex(core.AddressFromMessage(tx)), nil
}
return toHex(tx.Hash()), nil
}
func (self *XEth) sign(tx *types.Transaction, from []byte, didUnlock bool) error {
sig, err := self.accountManager.Sign(accounts.Account{Address: from}, tx.Hash())
if err == accounts.ErrLocked {
if didUnlock {
return fmt.Errorf("sender account still locked after successful unlock")
}
if !self.frontend.UnlockAccount(from) {
return fmt.Errorf("could not unlock sender account")
}
// retry signing, the account should now be unlocked.
return 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 }