diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 8118b4509c..e0a932f8b4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -17,8 +17,8 @@ }, { "ImportPath": "github.com/codegangsta/cli", - "Comment": "1.2.0-74-g50c77ec", - "Rev": "50c77ecec0068c9aef9d90ae0fd0fdf410041da3" + "Comment": "1.2.0-81-g3e09053", + "Rev": "3e0905345cd2c5366530dbcdce62457f2ce16e7c" }, { "ImportPath": "github.com/ethereum/ethash", diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/app.go b/Godeps/_workspace/src/github.com/codegangsta/cli/app.go index 928983ebd3..3e7d5a63ca 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/app.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/app.go @@ -43,9 +43,11 @@ type App struct { CommandNotFound func(context *Context, command string) // Compilation date Compiled time.Time - // Author + // List of all authors who contributed + Authors []Author + // Name of Author (Note: Use App.Authors, this is deprecated) Author string - // Author e-mail + // Email of Author (Note: Use App.Authors, this is deprecated) Email string // Writer writer to write output to Writer io.Writer @@ -70,14 +72,19 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), - Author: "Author", - Email: "unknown@email", + Author: "Dr. James", + Email: "who@gmail.com", + Authors: []Author{{"Jim", "jim@corporate.com"}, {"Hank", "hank@indiepalace.com"}}, Writer: os.Stdout, } } // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination func (a *App) Run(arguments []string) (err error) { + if a.Author != "" && a.Author != "" { + a.Authors = append(a.Authors, Author{a.Author, a.Email}) + } + if HelpPrinter == nil { defer func() { HelpPrinter = nil @@ -294,3 +301,19 @@ func (a *App) appendFlag(flag Flag) { a.Flags = append(a.Flags, flag) } } + +// Author represents someone who has contributed to a cli project. +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + +// String makes Author comply to the Stringer interface, to allow an easy print in the templating process +func (a Author) String() string { + e := "" + if a.Email != "" { + e = "<" + a.Email + "> " + } + + return fmt.Sprintf("%v %v", a.Name, e) +} diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go index fd2b0e8262..6143d364b5 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go @@ -21,6 +21,9 @@ func ExampleApp() { app.Action = func(c *cli.Context) { fmt.Printf("Hello %v\n", c.String("name")) } + app.Author = "Harrison" + app.Email = "harrison@lolwut.com" + app.Authors = []cli.Author{{"Oliver Allen", "oliver@toyshop.com"}} app.Run(os.Args) // Output: // Hello Jeremy diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/command.go b/Godeps/_workspace/src/github.com/codegangsta/cli/command.go index 5747e52e8e..07c919a87f 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/command.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/command.go @@ -119,7 +119,7 @@ func (c Command) Run(ctx *Context) error { // Returns true if Command.Name or Command.ShortName matches given name func (c Command) HasName(name string) bool { - return c.Name == name || c.ShortName == name + return c.Name == name || (c.ShortName != "" && c.ShortName == name) } func (c Command) startApp(ctx *Context) error { diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/help.go b/Godeps/_workspace/src/github.com/codegangsta/cli/help.go index bfb2788519..8d176556a2 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/help.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/help.go @@ -12,11 +12,10 @@ USAGE: {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] VERSION: - {{.Version}}{{if or .Author .Email}} + {{.Version}} -AUTHOR:{{if .Author}} - {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} - {{.Email}}{{end}}{{end}} +AUTHOR(S): + {{range .Authors}}{{ . }} {{end}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} @@ -112,6 +111,12 @@ func DefaultAppComplete(c *Context) { // Prints help for the given command func ShowCommandHelp(c *Context, command string) { + // show the subcommand help for a command with subcommands + if command == "" { + HelpPrinter(SubcommandHelpTemplate, c.App) + return + } + for _, c := range c.App.Commands { if c.HasName(command) { HelpPrinter(CommandHelpTemplate, c) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 3e9fa77991..646dc8376e 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -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() } diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 44d1d72f17..427114cbde 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -1,43 +1,36 @@ package accounts import ( + "io/ioutil" + "os" "testing" + "time" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" - "github.com/ethereum/go-ethereum/ethutil" - "time" ) -func TestAccountManager(t *testing.T) { - ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 100) +func TestSign(t *testing.T) { + dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain) + defer os.RemoveAll(dir) + + am := NewManager(ks) pass := "" // not used but required by API a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) - _, err = am.SignLocked(a1, pass, toSign) + am.Unlock(a1.Address, "") + + _, err = am.Sign(a1, toSign) if err != nil { t.Fatal(err) } - - // Cleanup - time.Sleep(time.Millisecond * 150) // wait for locking - - accounts, err := am.Accounts() - if err != nil { - t.Fatal(err) - } - for _, account := range accounts { - err := am.DeleteAccount(account.Address, pass) - if err != nil { - t.Fatal(err) - } - } } -func TestAccountManagerLocking(t *testing.T) { - ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 200) +func TestTimedUnlock(t *testing.T) { + dir, ks := tmpKeyStore(t, crypto.NewKeyStorePassphrase) + defer os.RemoveAll(dir) + + am := NewManager(ks) pass := "foo" a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -45,38 +38,32 @@ func TestAccountManagerLocking(t *testing.T) { // Signing without passphrase fails because account is locked _, err = am.Sign(a1, toSign) if err != ErrLocked { - t.Fatal(err) + t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) } // Signing with passphrase works - _, err = am.SignLocked(a1, pass, toSign) - if err != nil { + if err = am.TimedUnlock(a1.Address, pass, 100*time.Millisecond); err != nil { t.Fatal(err) } // Signing without passphrase works because account is temp unlocked _, err = am.Sign(a1, toSign) if err != nil { - t.Fatal(err) + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) } - // Signing without passphrase fails after automatic locking - time.Sleep(time.Millisecond * time.Duration(250)) - + // Signing fails again after automatic locking + time.Sleep(150 * time.Millisecond) _, err = am.Sign(a1, toSign) if err != ErrLocked { - t.Fatal(err) + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) } +} - // Cleanup - accounts, err := am.Accounts() +func tmpKeyStore(t *testing.T, new func(string) crypto.KeyStore2) (string, crypto.KeyStore2) { + d, err := ioutil.TempDir("", "eth-keystore-test") if err != nil { t.Fatal(err) } - for _, account := range accounts { - err := am.DeleteAccount(account.Address, pass) - if err != nil { - t.Fatal(err) - } - } + return d, new(d) } diff --git a/cmd/blocktest/flags.go b/cmd/blocktest/flags.go deleted file mode 100644 index c811e5b857..0000000000 --- a/cmd/blocktest/flags.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - go-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with go-ethereum. If not, see . -*/ -/** - * @authors - * Gustav Simonsson - */ -package main - -import ( - "flag" - "fmt" - "os" -) - -var ( - TestFile string -) - -func Init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s \n", os.Args[0]) - flag.PrintDefaults() - } - flag.Parse() - - TestFile = flag.Arg(0) -} diff --git a/cmd/blocktest/main.go b/cmd/blocktest/main.go index 4a05b8bee8..ec2779e2ec 100644 --- a/cmd/blocktest/main.go +++ b/cmd/blocktest/main.go @@ -25,34 +25,26 @@ package main import ( "bytes" - "crypto/ecdsa" "encoding/hex" "encoding/json" + "flag" "fmt" "io/ioutil" "log" "math/big" - "path" + "os" "runtime" - "strconv" "strings" - "time" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/core" types "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/rlp" ) -const ( - ClientIdentifier = "Ethereum(G)" - Version = "0.8.6" -) - type Account struct { Balance string Code string @@ -78,6 +70,7 @@ type BlockHeader struct { TransactionsTrie string UncleHash string } + type Tx struct { Data string GasLimit string @@ -103,103 +96,44 @@ type Test struct { Pre map[string]Account } -var ( - Identifier string - KeyRing string - DiffTool bool - DiffType string - KeyStore string - StartRpc bool - StartWebSockets bool - RpcListenAddress string - RpcPort int - WsPort int - OutboundPort string - ShowGenesis bool - AddPeer string - MaxPeer int - GenAddr bool - BootNodes string - NodeKey *ecdsa.PrivateKey - NAT nat.Interface - SecretFile string - ExportDir string - NonInteractive bool - Datadir string - LogFile string - ConfigFile string - DebugFile string - LogLevel int - LogFormat string - Dump bool - DumpHash string - DumpNumber int - VmType int - ImportChain string - SHH bool - Dial bool - PrintVersion bool - MinerThreads int -) - -// flags specific to cli client -var ( - StartMining bool - StartJsConsole bool - InputFile string -) - func main() { - init_vars() - - Init() - - if len(TestFile) < 1 { - log.Fatal("Please specify test file") - } - blocks, err := loadBlocksFromTestFile(TestFile) - if err != nil { - panic(err) + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "%s \n", os.Args[0]) + flag.PrintDefaults() } + flag.Parse() runtime.GOMAXPROCS(runtime.NumCPU()) + logger.AddLogSystem(logger.NewStdLogSystem(os.Stderr, log.LstdFlags, logger.DebugDetailLevel)) + defer func() { logger.Flush() }() - defer func() { - logger.Flush() - }() + if len(os.Args) < 2 { + utils.Fatalf("Please specify a test file as the first argument.") + } + blocks, err := loadBlocksFromTestFile(os.Args[1]) + if err != nil { + utils.Fatalf("Could not load blocks: %v", err) + } - utils.HandleInterrupt() + chain := memchain() + chain.ResetWithGenesisBlock(blocks[0]) + if err = chain.InsertChain(types.Blocks{blocks[1]}); err != nil { + utils.Fatalf("Error: %v", err) + } else { + fmt.Println("PASS") + } +} - utils.InitConfig(VmType, ConfigFile, Datadir, "ethblocktest") - - ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(ClientIdentifier, Version), - KeyStore: KeyStore, - DataDir: Datadir, - LogFile: LogFile, - LogLevel: LogLevel, - LogFormat: LogFormat, - MaxPeers: MaxPeer, - Port: OutboundPort, - NAT: NAT, - KeyRing: KeyRing, - Shh: true, - Dial: Dial, - BootNodes: BootNodes, - NodeKey: NodeKey, - MinerThreads: MinerThreads, - }) - - utils.StartRpc(ethereum, RpcListenAddress, RpcPort) - utils.StartEthereum(ethereum) - - ethereum.ChainManager().ResetWithGenesisBlock(blocks[0]) - - // fmt.Println("HURR: ", hex.EncodeToString(ethutil.Encode(blocks[0].RlpData()))) - - go ethereum.ChainManager().InsertChain(types.Blocks{blocks[1]}) - fmt.Println("OK! ") - ethereum.WaitForShutdown() +func memchain() *core.ChainManager { + blockdb, err := ethdb.NewMemDatabase() + if err != nil { + utils.Fatalf("Could not create in-memory database: %v", err) + } + statedb, err := ethdb.NewMemDatabase() + if err != nil { + utils.Fatalf("Could not create in-memory database: %v", err) + } + return core.NewChainManager(blockdb, statedb, new(event.TypeMux)) } func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { @@ -207,9 +141,8 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { if err != nil { return } - bt := *new(map[string]Test) - err = json.Unmarshal(fileContent, &bt) - if err != nil { + bt := make(map[string]Test) + if err = json.Unmarshal(fileContent, &bt); err != nil { return } @@ -254,6 +187,9 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { } gb := types.NewBlockWithHeader(gbh) + //gb.uncles = *new([]*types.Header) + //gb.transactions = *new(types.Transactions) + gb.Td = new(big.Int) gb.Reward = new(big.Int) testBlock := new(types.Block) @@ -272,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")) } diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index e3165d3f5c..5432fb9b1b 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -22,11 +22,9 @@ import ( "fmt" "io/ioutil" "os" - "os/signal" "path" "strings" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethutil" @@ -37,51 +35,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() diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 12e205f375..10d9c8fa4e 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -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 := eth.AccountManager() + // 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 +} diff --git a/cmd/evm/main.go b/cmd/evm/main.go index d6a93460e6..960558bb4f 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -59,8 +59,6 @@ func main() { logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(*loglevel))) - ethutil.ReadConfig("/tmp/evmtest", "/tmp/evm", "") - db, _ := ethdb.NewMemDatabase() statedb := state.New(nil, db) sender := statedb.NewStateObject([]byte("sender")) diff --git a/cmd/mist/assets/examples/coin.html b/cmd/mist/assets/examples/coin.html index 509a9aeeb3..96f2299a5f 100644 --- a/cmd/mist/assets/examples/coin.html +++ b/cmd/mist/assets/examples/coin.html @@ -35,7 +35,7 @@ var web3 = require('web3'); var eth = web3.eth; - web3.setProvider(new web3.providers.HttpSyncProvider('http://localhost:8545')); + web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')); var desc = [{ "name": "balance(address)", "type": "function", @@ -72,12 +72,13 @@ // deploy if not exist if (address == null) { var code = "0x60056013565b61014f8061003a6000396000f35b620f42406000600033600160a060020a0316815260200190815260200160002081905550560060e060020a600035048063d0679d3414610020578063e3d670d71461003457005b61002e600435602435610049565b60006000f35b61003f600435610129565b8060005260206000f35b806000600033600160a060020a03168152602001908152602001600020541061007157610076565b610125565b806000600033600160a060020a03168152602001908152602001600020908154039081905550806000600084600160a060020a031681526020019081526020016000209081540190819055508033600160a060020a03167fb52dda022b6c1a1f40905a85f257f689aa5d69d850e49cf939d688fbe5af594660006000a38082600160a060020a03167fb52dda022b6c1a1f40905a85f257f689aa5d69d850e49cf939d688fbe5af594660006000a35b5050565b60006000600083600160a060020a0316815260200190815260200160002054905091905056"; - address = web3.eth.transact({data: code}); + address = web3.eth.transact({from: eth.coinbase, data: code}); localStorage.setItem("address", address); } document.querySelector("#contract_addr").innerHTML = address; - var contract = web3.eth.contract(address, desc); + var Contract = web3.eth.contract(desc); + contract = new Contract(address); contract.Changed({from: eth.coinbase}).changed(function() { refresh(); }); @@ -88,7 +89,7 @@ var table = document.querySelector("#table_body"); table.innerHTML = ""; // clear - var storage = eth.storageAt(address); + var storage = eth.getStorage(address); table.innerHTML = ""; for( var item in storage ) { table.innerHTML += ""+item+""+web3.toDecimal(storage[item])+""; diff --git a/cmd/mist/assets/ext/ethereum.js/dist/ethereum.js b/cmd/mist/assets/ext/ethereum.js/dist/ethereum.js index 5b7d872706..c0b37641c7 100644 --- a/cmd/mist/assets/ext/ethereum.js/dist/ethereum.js +++ b/cmd/mist/assets/ext/ethereum.js/dist/ethereum.js @@ -22,34 +22,57 @@ require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof requ * @date 2014 */ -var utils = require('./utils'); +var utils = require('../utils/utils'); +var c = require('../utils/config'); var types = require('./types'); -var c = require('./const'); var f = require('./formatters'); -var displayTypeError = function (type) { - console.error('parser does not support type: ' + type); +/** + * throw incorrect type error + * + * @method throwTypeError + * @param {String} type + * @throws incorrect type error + */ +var throwTypeError = function (type) { + throw new Error('parser does not support type: ' + type); }; -/// This method should be called if we want to check if givent type is an array type -/// @returns true if it is, otherwise false -var arrayType = function (type) { +/** This method should be called if we want to check if givent type is an array type + * + * @method isArrayType + * @param {String} type name + * @returns {Boolean} true if it is, otherwise false + */ +var isArrayType = function (type) { return type.slice(-2) === '[]'; }; +/** + * This method should be called to return dynamic type length in hex + * + * @method dynamicTypeBytes + * @param {String} type + * @param {String|Array} dynamic type + * @return {String} length of dynamic type in hex or empty string if type is not dynamic + */ var dynamicTypeBytes = function (type, value) { // TODO: decide what to do with array of strings - if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. + if (isArrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. return f.formatInputInt(value.length); return ""; }; var inputTypes = types.inputTypes(); -/// Formats input params to bytes -/// @param abi contract method inputs -/// @param array of params that will be formatted to bytes -/// @returns bytes representation of input params +/** + * Formats input params to bytes + * + * @method formatInput + * @param {Array} abi inputs of method + * @param {Array} params that will be formatted to bytes + * @returns bytes representation of input params + */ var formatInput = function (inputs, params) { var bytes = ""; var toAppendConstant = ""; @@ -67,12 +90,12 @@ var formatInput = function (inputs, params) { typeMatch = inputTypes[j].type(inputs[i].type, params[i]); } if (!typeMatch) { - displayTypeError(inputs[i].type); + throwTypeError(inputs[i].type); } var formatter = inputTypes[j - 1].format; - if (arrayType(inputs[i].type)) + if (isArrayType(inputs[i].type)) toAppendArrayContent += params[i].reduce(function (acc, curr) { return acc + formatter(curr); }, ""); @@ -87,18 +110,29 @@ var formatInput = function (inputs, params) { return bytes; }; +/** + * This method should be called to predict the length of dynamic type + * + * @method dynamicBytesLength + * @param {String} type + * @returns {Number} length of dynamic type, 0 or multiplication of ETH_PADDING (32) + */ var dynamicBytesLength = function (type) { - if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. + if (isArrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. return c.ETH_PADDING * 2; return 0; }; var outputTypes = types.outputTypes(); -/// Formats output bytes back to param list -/// @param contract abi method outputs -/// @param bytes representtion of output -/// @returns array of output params +/** + * Formats output bytes back to param list + * + * @method formatOutput + * @param {Array} abi outputs of method + * @param {String} bytes represention of output + * @returns {Array} output params + */ var formatOutput = function (outs, output) { output = output.slice(2); @@ -120,11 +154,11 @@ var formatOutput = function (outs, output) { } if (!typeMatch) { - displayTypeError(outs[i].type); + throwTypeError(outs[i].type); } var formatter = outputTypes[j - 1].format; - if (arrayType(outs[i].type)) { + if (isArrayType(outs[i].type)) { var size = f.formatOutputUInt(dynamicPart.slice(0, padding)); dynamicPart = dynamicPart.slice(padding); var array = []; @@ -147,9 +181,14 @@ var formatOutput = function (outs, output) { return result; }; -/// @param json abi for contract -/// @returns input parser object for given json abi -/// TODO: refactor creating the parser, do not double logic from contract +/** + * Should be called to create input parser for contract with given abi + * + * @method inputParser + * @param {Array} contract abi + * @returns {Object} input parser object for given json abi + * TODO: refactor creating the parser, do not double logic from contract + */ var inputParser = function (json) { var parser = {}; json.forEach(function (method) { @@ -171,8 +210,13 @@ var inputParser = function (json) { return parser; }; -/// @param json abi for contract -/// @returns output parser for given json abi +/** + * Should be called to create output parser for contract with given abi + * + * @method outputParser + * @param {Array} contract abi + * @returns {Object} output parser for given json abi + */ var outputParser = function (json) { var parser = {}; json.forEach(function (method) { @@ -201,7 +245,7 @@ module.exports = { formatOutput: formatOutput }; -},{"./const":2,"./formatters":8,"./types":15,"./utils":16}],2:[function(require,module,exports){ +},{"../utils/config":4,"../utils/utils":5,"./formatters":2,"./types":3}],2:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -218,12 +262,322 @@ module.exports = { You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file const.js +/** @file formatters.js * @authors: * Marek Kotewicz * @date 2015 */ +if ("build" !== 'build') {/* + var BigNumber = require('bignumber.js'); // jshint ignore:line +*/} + +var utils = require('../utils/utils'); +var c = require('../utils/config'); + +/** + * Should be called to pad string to expected length + * + * @method padLeft + * @param {String} string to be padded + * @param {Number} characters that result string should have + * @param {String} sign, by default 0 + * @returns {String} right aligned string + */ +var padLeft = function (string, chars, sign) { + return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; +}; + +/** + * Formats input value to byte representation of int + * If value is negative, return it's two's complement + * If the value is floating point, round it down + * + * @method formatInputInt + * @param {String|Number|BigNumber} value that needs to be formatted + * @returns {String} right-aligned byte representation of int + */ +var formatInputInt = function (value) { + var padding = c.ETH_PADDING * 2; + BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); + return padLeft(utils.toTwosComplement(value).round().toString(16), padding); +}; + +/** + * Formats input value to byte representation of string + * + * @method formatInputString + * @param {String} + * @returns {String} left-algined byte representation of string + */ +var formatInputString = function (value) { + return utils.fromAscii(value, c.ETH_PADDING).substr(2); +}; + +/** + * Formats input value to byte representation of bool + * + * @method formatInputBool + * @param {Boolean} + * @returns {String} right-aligned byte representation bool + */ +var formatInputBool = function (value) { + return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); +}; + +/** + * Formats input value to byte representation of real + * Values are multiplied by 2^m and encoded as integers + * + * @method formatInputReal + * @param {String|Number|BigNumber} + * @returns {String} byte representation of real + */ +var formatInputReal = function (value) { + return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); +}; + +/** + * Check if input value is negative + * + * @method signedIsNegative + * @param {String} value is hex format + * @returns {Boolean} true if it is negative, otherwise false + */ +var signedIsNegative = function (value) { + return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; +}; + +/** + * Formats right-aligned output bytes to int + * + * @method formatOutputInt + * @param {String} bytes + * @returns {BigNumber} right-aligned output bytes formatted to big number + */ +var formatOutputInt = function (value) { + + value = value || "0"; + + // check if it's negative number + // it it is, return two's complement + if (signedIsNegative(value)) { + return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); + } + return new BigNumber(value, 16); +}; + +/** + * Formats right-aligned output bytes to uint + * + * @method formatOutputUInt + * @param {String} bytes + * @returns {BigNumeber} right-aligned output bytes formatted to uint + */ +var formatOutputUInt = function (value) { + value = value || "0"; + return new BigNumber(value, 16); +}; + +/** + * Formats right-aligned output bytes to real + * + * @method formatOutputReal + * @param {String} + * @returns {BigNumber} input bytes formatted to real + */ +var formatOutputReal = function (value) { + return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/** + * Formats right-aligned output bytes to ureal + * + * @method formatOutputUReal + * @param {String} + * @returns {BigNumber} input bytes formatted to ureal + */ +var formatOutputUReal = function (value) { + return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/** + * Should be used to format output hash + * + * @method formatOutputHash + * @param {String} + * @returns {String} right-aligned output bytes formatted to hex + */ +var formatOutputHash = function (value) { + return "0x" + value; +}; + +/** + * Should be used to format output bool + * + * @method formatOutputBool + * @param {String} + * @returns {Boolean} right-aligned input bytes formatted to bool + */ +var formatOutputBool = function (value) { + return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; +}; + +/** + * Should be used to format output string + * + * @method formatOutputString + * @param {Sttring} left-aligned hex representation of string + * @returns {String} ascii string + */ +var formatOutputString = function (value) { + return utils.toAscii(value); +}; + +/** + * Should be used to format output address + * + * @method formatOutputAddress + * @param {String} right-aligned input bytes + * @returns {String} address + */ +var formatOutputAddress = function (value) { + return "0x" + value.slice(value.length - 40, value.length); +}; + +module.exports = { + formatInputInt: formatInputInt, + formatInputString: formatInputString, + formatInputBool: formatInputBool, + formatInputReal: formatInputReal, + formatOutputInt: formatOutputInt, + formatOutputUInt: formatOutputUInt, + formatOutputReal: formatOutputReal, + formatOutputUReal: formatOutputUReal, + formatOutputHash: formatOutputHash, + formatOutputBool: formatOutputBool, + formatOutputString: formatOutputString, + formatOutputAddress: formatOutputAddress +}; + + +},{"../utils/config":4,"../utils/utils":5}],3:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file types.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +var f = require('./formatters'); + +/// @param expected type prefix (string) +/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false +var prefixedType = function (prefix) { + return function (type) { + return type.indexOf(prefix) === 0; + }; +}; + +/// @param expected type name (string) +/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false +var namedType = function (name) { + return function (type) { + return name === type; + }; +}; + +/// Setups input formatters for solidity types +/// @returns an array of input formatters +var inputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatInputInt }, + { type: prefixedType('int'), format: f.formatInputInt }, + { type: prefixedType('hash'), format: f.formatInputInt }, + { type: prefixedType('string'), format: f.formatInputString }, + { type: prefixedType('real'), format: f.formatInputReal }, + { type: prefixedType('ureal'), format: f.formatInputReal }, + { type: namedType('address'), format: f.formatInputInt }, + { type: namedType('bool'), format: f.formatInputBool } + ]; +}; + +/// Setups output formaters for solidity types +/// @returns an array of output formatters +var outputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatOutputUInt }, + { type: prefixedType('int'), format: f.formatOutputInt }, + { type: prefixedType('hash'), format: f.formatOutputHash }, + { type: prefixedType('string'), format: f.formatOutputString }, + { type: prefixedType('real'), format: f.formatOutputReal }, + { type: prefixedType('ureal'), format: f.formatOutputUReal }, + { type: namedType('address'), format: f.formatOutputAddress }, + { type: namedType('bool'), format: f.formatOutputBool } + ]; +}; + +module.exports = { + prefixedType: prefixedType, + namedType: namedType, + inputTypes: inputTypes, + outputTypes: outputTypes +}; + + +},{"./formatters":2}],4:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file config.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +/** + * Utils + * + * @module utils + */ + +/** + * Utility functions + * + * @class [utils] config + * @constructor + */ + /// required to define ETH_BIGNUMBER_ROUNDING_MODE if ("build" !== 'build') {/* var BigNumber = require('bignumber.js'); // jshint ignore:line @@ -256,11 +610,713 @@ module.exports = { ETH_SIGNATURE_LENGTH: 4, ETH_UNITS: ETH_UNITS, ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN }, - ETH_POLLING_TIMEOUT: 1000 + ETH_POLLING_TIMEOUT: 1000, + ETH_DEFAULTBLOCK: 'latest' }; -},{}],3:[function(require,module,exports){ +},{}],5:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file utils.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +/** + * Utils + * + * @module utils + */ + +/** + * Utility functions + * + * @class [utils] utils + * @constructor + */ + +if ("build" !== 'build') {/* + var BigNumber = require('bignumber.js'); // jshint ignore:line +*/} + +var unitMap = { + 'wei': '1', + 'kwei': '1000', + 'ada': '1000', + 'mwei': '1000000', + 'babbage': '1000000', + 'gwei': '1000000000', + 'shannon': '1000000000', + 'szabo': '1000000000000', + 'finney': '1000000000000000', + 'ether': '1000000000000000000', + 'kether': '1000000000000000000000', + 'grand': '1000000000000000000000', + 'einstein': '1000000000000000000000', + 'mether': '1000000000000000000000000', + 'gether': '1000000000000000000000000000', + 'tether': '1000000000000000000000000000000' +}; + + +/** Finds first index of array element matching pattern + * + * @method findIndex + * @param {Array} + * @param {Function} pattern + * @returns {Number} index of element + */ +var findIndex = function (array, callback) { + var end = false; + var i = 0; + for (; i < array.length && !end; i++) { + end = callback(array[i]); + } + return end ? i - 1 : -1; +}; + +/** + * Should be called to get sting from it's hex representation + * + * @method toAscii + * @param {String} string in hex + * @returns {String} ascii string representation of hex value + */ +var toAscii = function(hex) { +// Find termination + var str = ""; + var i = 0, l = hex.length; + if (hex.substring(0, 2) === '0x') { + i = 2; + } + for (; i < l; i+=2) { + var code = parseInt(hex.substr(i, 2), 16); + if (code === 0) { + break; + } + + str += String.fromCharCode(code); + } + + return str; +}; + +/** + * Shold be called to get hex representation (prefixed by 0x) of ascii string + * + * @method fromAscii + * @param {String} string + * @returns {String} hex representation of input string + */ +var toHexNative = function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var n = str.charCodeAt(i).toString(16); + hex += n.length < 2 ? '0' + n : n; + } + + return hex; +}; + +/** + * Shold be called to get hex representation (prefixed by 0x) of ascii string + * + * @method fromAscii + * @param {String} string + * @param {Number} optional padding + * @returns {String} hex representation of input string + */ +var fromAscii = function(str, pad) { + pad = pad === undefined ? 0 : pad; + var hex = toHexNative(str); + while (hex.length < pad*2) + hex += "00"; + return "0x" + hex; +}; + +/** + * Should be called to get display name of contract function + * + * @method extractDisplayName + * @param {String} name of function/event + * @returns {String} display name for function/event eg. multiply(uint256) -> multiply + */ +var extractDisplayName = function (name) { + var length = name.indexOf('('); + return length !== -1 ? name.substr(0, length) : name; +}; + +/// @returns overloaded part of function/event name +var extractTypeName = function (name) { + /// TODO: make it invulnerable + var length = name.indexOf('('); + return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)).replace(' ', '') : ""; +}; + +/** + * Filters all functions from input abi + * + * @method filterFunctions + * @param {Array} abi + * @returns {Array} abi array with filtered objects of type 'function' + */ +var filterFunctions = function (json) { + return json.filter(function (current) { + return current.type === 'function'; + }); +}; + +/** + * Filters all events from input abi + * + * @method filterEvents + * @param {Array} abi + * @returns {Array} abi array with filtered objects of type 'event' + */ +var filterEvents = function (json) { + return json.filter(function (current) { + return current.type === 'event'; + }); +}; + +/** + * Converts value to it's decimal representation in string + * + * @method toDecimal + * @param {String|Number|BigNumber} + * @return {String} + */ +var toDecimal = function (value) { + return toBigNumber(value).toNumber(); +}; + +/** + * Converts value to it's hex representation + * + * @method fromDecimal + * @param {String|Number|BigNumber} + * @return {String} + */ +var fromDecimal = function (value) { + var number = toBigNumber(value); + var result = number.toString(16); + + return (number.lessThan(0)) + ? '-0x' + result.substr(1) + : '0x' + result; +}; + +/** + * Auto converts any given value into it's hex representation. + * + * And even stringifys objects before. + * + * @method toHex + * @param {String|Number|BigNumber|Object} + * @return {String} + */ +var toHex = function (val) { + /*jshint maxcomplexity:5 */ + + if(typeof val === 'boolean') + return val; + + if(isBigNumber(val)) + return fromDecimal(val); + + if(typeof val === 'object') + return fromAscii(JSON.stringify(val)); + + if(isString(val) && val.indexOf('0x') === 0) + return val; + // if its a negative number, pass it through fromDecimal + if(isString(val) && val.indexOf('-0x') === 0) + return fromDecimal(val); + + if(isString(val) && !isFinite(val)) + return fromAscii(val); + + if(isFinite(val)) + return fromDecimal(val); + + return val; +}; + +/** + * Returns value of unit in Wei + * + * @method getValueOfUnit + * @param {String} unit the unit to convert to, default ether + * @returns {BigNumber} value of the unit (in Wei) + * @throws error if the unit is not correct:w + */ +var getValueOfUnit = function (unit) { + unit = unit ? unit.toLowerCase() : 'ether'; + var unitValue = unitMap[unit]; + if (unitValue === undefined) { + throw new Error('This unit doesn\'t exists, please use the one of the following units' + JSON.stringify(unitMap, null, 2)); + } + return new BigNumber(unitValue, 10); +}; + +/** + * Takes a number of wei and converts it to any other ether unit. + * + * Possible units are: + * - kwei/ada + * - mwei/babbage + * - gwei/shannon + * - szabo + * - finney + * - ether + * - kether/grand/einstein + * - mether + * - gether + * - tether + * + * @method fromWei + * @param {Number|String} number can be a number, number string or a HEX of a decimal + * @param {String} unit the unit to convert to, default ether + * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number +*/ +var fromWei = function(number, unit) { + var returnValue = toBigNumber(number).dividedBy(getValueOfUnit(unit)); + + return (isBigNumber(number)) + ? returnValue : returnValue.toString(10); +}; + +/** + * Takes a number of a unit and converts it to wei. + * + * Possible units are: + * - kwei/ada + * - mwei/babbage + * - gwei/shannon + * - szabo + * - finney + * - ether + * - kether/grand/einstein + * - mether + * - gether + * - tether + * + * @method toWei + * @param {Number|String|BigNumber} number can be a number, number string or a HEX of a decimal + * @param {String} unit the unit to convert from, default ether + * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number +*/ +var toWei = function(number, unit) { + var returnValue = toBigNumber(number).times(getValueOfUnit(unit)); + + return (isBigNumber(number)) + ? returnValue : returnValue.toString(10); +}; + +/** + * Takes an input and transforms it into an bignumber + * + * @method toBigNumber + * @param {Number|String|BigNumber} a number, string, HEX string or BigNumber + * @return {BigNumber} BigNumber +*/ +var toBigNumber = function(number) { + number = number || 0; + if (isBigNumber(number)) + return number; + + return (isString(number) && (number.indexOf('0x') === 0 || number.indexOf('-0x') === 0)) + ? new BigNumber(number.replace('0x',''), 16) + : new BigNumber(number.toString(10), 10); +}; + +/** + * Takes and input transforms it into bignumber and if it is negative value, into two's complement + * + * @method toTwosComplement + * @param {Number|String|BigNumber} + * @return {BigNumber} + */ +var toTwosComplement = function (number) { + var bigNumber = toBigNumber(number); + if (bigNumber.lessThan(0)) { + return new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(bigNumber).plus(1); + } + return bigNumber; +}; + +/** + * Checks if the given string has proper length + * + * @method isAddress + * @param {String} address the given HEX adress + * @return {Boolean} +*/ +var isAddress = function(address) { + if (!isString(address)) { + return false; + } + + return ((address.indexOf('0x') === 0 && address.length === 42) || + (address.indexOf('0x') === -1 && address.length === 40)); +}; + +/** + * Returns true if object is BigNumber, otherwise false + * + * @method isBigNumber + * @param {Object} + * @return {Boolean} + */ +var isBigNumber = function (object) { + return object instanceof BigNumber || + (object && object.constructor && object.constructor.name === 'BigNumber'); +}; + +/** + * Returns true if object is string, otherwise false + * + * @method isString + * @param {Object} + * @return {Boolean} + */ +var isString = function (object) { + return typeof object === 'string' || + (object && object.constructor && object.constructor.name === 'String'); +}; + +/** + * Returns true if object is function, otherwise false + * + * @method isFunction + * @param {Object} + * @return {Boolean} + */ +var isFunction = function (object) { + return typeof object === 'function'; +}; + +module.exports = { + findIndex: findIndex, + toHex: toHex, + toDecimal: toDecimal, + fromDecimal: fromDecimal, + toAscii: toAscii, + fromAscii: fromAscii, + extractDisplayName: extractDisplayName, + extractTypeName: extractTypeName, + filterFunctions: filterFunctions, + filterEvents: filterEvents, + toWei: toWei, + fromWei: fromWei, + toBigNumber: toBigNumber, + toTwosComplement: toTwosComplement, + isBigNumber: isBigNumber, + isAddress: isAddress, + isFunction: isFunction, + isString: isString +}; + + +},{}],6:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file web3.js + * @authors: + * Jeffrey Wilcke + * Marek Kotewicz + * Marian Oancea + * Gav Wood + * @date 2014 + */ + +var net = require('./web3/net'); +var eth = require('./web3/eth'); +var db = require('./web3/db'); +var shh = require('./web3/shh'); +var watches = require('./web3/watches'); +var filter = require('./web3/filter'); +var utils = require('./utils/utils'); +var formatters = require('./solidity/formatters'); +var requestManager = require('./web3/requestmanager'); +var c = require('./utils/config'); + +/// @returns an array of objects describing web3 api methods +var web3Methods = function () { + return [ + { name: 'sha3', call: 'web3_sha3' } + ]; +}; + +/// creates methods in a given object based on method description on input +/// setups api calls for these methods +var setupMethods = function (obj, methods) { + methods.forEach(function (method) { + // allow for object methods 'myObject.method' + var objectMethods = method.name.split('.'), + callFunction = function () { + /*jshint maxcomplexity:8 */ + + var callback = null, + args = Array.prototype.slice.call(arguments), + call = typeof method.call === 'function' ? method.call(args) : method.call; + + // get the callback if one is available + if(typeof args[args.length-1] === 'function'){ + callback = args[args.length-1]; + Array.prototype.pop.call(args); + } + + // add the defaultBlock if not given + if(method.addDefaultblock) { + if(args.length !== method.addDefaultblock) + Array.prototype.push.call(args, (isFinite(c.ETH_DEFAULTBLOCK) ? utils.fromDecimal(c.ETH_DEFAULTBLOCK) : c.ETH_DEFAULTBLOCK)); + else + args[args.length-1] = isFinite(args[args.length-1]) ? utils.fromDecimal(args[args.length-1]) : args[args.length-1]; + } + + // show deprecated warning + if(method.newMethod) + console.warn('This method is deprecated please use web3.'+ method.newMethod +'() instead.'); + + return web3.manager.send({ + method: call, + params: args, + outputFormatter: method.outputFormatter, + inputFormatter: method.inputFormatter, + addDefaultblock: method.addDefaultblock + }, callback); + }; + + if(objectMethods.length > 1) { + if(!obj[objectMethods[0]]) + obj[objectMethods[0]] = {}; + + obj[objectMethods[0]][objectMethods[1]] = callFunction; + + } else { + + obj[objectMethods[0]] = callFunction; + } + + }); +}; + +/// creates properties in a given object based on properties description on input +/// setups api calls for these properties +var setupProperties = function (obj, properties) { + properties.forEach(function (property) { + var proto = {}; + proto.get = function () { + + // show deprecated warning + if(property.newProperty) + console.warn('This property is deprecated please use web3.'+ property.newProperty +' instead.'); + + + return web3.manager.send({ + method: property.getter, + outputFormatter: property.outputFormatter + }); + }; + + if (property.setter) { + proto.set = function (val) { + + // show deprecated warning + if(property.newProperty) + console.warn('This property is deprecated please use web3.'+ property.newProperty +' instead.'); + + return web3.manager.send({ + method: property.setter, + params: [val], + inputFormatter: property.inputFormatter + }); + }; + } + + proto.enumerable = !property.newProperty; + Object.defineProperty(obj, property.name, proto); + + }); +}; + +/*jshint maxparams:4 */ +var startPolling = function (method, id, callback, uninstall) { + web3.manager.startPolling({ + method: method, + params: [id] + }, id, callback, uninstall); +}; +/*jshint maxparams:3 */ + +var stopPolling = function (id) { + web3.manager.stopPolling(id); +}; + +var ethWatch = { + startPolling: startPolling.bind(null, 'eth_getFilterChanges'), + stopPolling: stopPolling +}; + +var shhWatch = { + startPolling: startPolling.bind(null, 'shh_getFilterChanges'), + stopPolling: stopPolling +}; + +/// setups web3 object, and it's in-browser executed methods +var web3 = { + manager: requestManager(), + providers: {}, + + setProvider: function (provider) { + web3.manager.setProvider(provider); + }, + + /// Should be called to reset state of web3 object + /// Resets everything except manager + reset: function () { + web3.manager.reset(); + }, + + /// @returns hex string of the input + toHex: utils.toHex, + + /// @returns ascii string representation of hex value prefixed with 0x + toAscii: utils.toAscii, + + /// @returns hex representation (prefixed by 0x) of ascii string + fromAscii: utils.fromAscii, + + /// @returns decimal representaton of hex value prefixed by 0x + toDecimal: utils.toDecimal, + + /// @returns hex representation (prefixed by 0x) of decimal value + fromDecimal: utils.fromDecimal, + + /// @returns a BigNumber object + toBigNumber: utils.toBigNumber, + + toWei: utils.toWei, + fromWei: utils.fromWei, + isAddress: utils.isAddress, + + // provide network information + net: { + // peerCount: + }, + + + /// eth object prototype + eth: { + // DEPRECATED + contractFromAbi: function (abi) { + console.warn('Initiating a contract like this is deprecated please use var MyContract = eth.contract(abi); new MyContract(address); instead.'); + + return function(addr) { + // Default to address of Config. TODO: rremove prior to genesis. + addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; + var ret = web3.eth.contract(addr, abi); + ret.address = addr; + return ret; + }; + }, + + /// @param filter may be a string, object or event + /// @param eventParams is optional, this is an object with optional event eventParams params + /// @param options is optional, this is an object with optional event options ('max'...) + /*jshint maxparams:4 */ + filter: function (fil, eventParams, options) { + + // if its event, treat it differently + if (fil._isEvent) + return fil(eventParams, options); + + return filter(fil, ethWatch, formatters.outputLogFormatter); + }, + // DEPRECATED + watch: function (fil, eventParams, options) { + console.warn('eth.watch() is deprecated please use eth.filter() instead.'); + return this.filter(fil, eventParams, options); + } + /*jshint maxparams:3 */ + }, + + /// db object prototype + db: {}, + + /// shh object prototype + shh: { + /// @param filter may be a string, object or event + filter: function (fil) { + return filter(fil, shhWatch, formatters.outputPostFormatter); + }, + // DEPRECATED + watch: function (fil) { + console.warn('shh.watch() is deprecated please use shh.filter() instead.'); + return this.filter(fil); + } + } +}; + + +// ADD defaultblock +Object.defineProperty(web3.eth, 'defaultBlock', { + get: function () { + return c.ETH_DEFAULTBLOCK; + }, + set: function (val) { + c.ETH_DEFAULTBLOCK = val; + return c.ETH_DEFAULTBLOCK; + } +}); + + +/// setups all api methods +setupMethods(web3, web3Methods()); +setupMethods(web3.net, net.methods); +setupProperties(web3.net, net.properties); +setupMethods(web3.eth, eth.methods); +setupProperties(web3.eth, eth.properties); +setupMethods(web3.db, db.methods()); +setupMethods(web3.shh, shh.methods()); +setupMethods(ethWatch, watches.eth()); +setupMethods(shhWatch, watches.shh()); + +module.exports = web3; + + +},{"./solidity/formatters":2,"./utils/config":4,"./utils/utils":5,"./web3/db":8,"./web3/eth":9,"./web3/filter":11,"./web3/net":15,"./web3/requestmanager":17,"./web3/shh":18,"./web3/watches":20}],7:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -283,9 +1339,9 @@ module.exports = { * @date 2014 */ -var web3 = require('./web3'); -var abi = require('./abi'); -var utils = require('./utils'); +var web3 = require('../web3'); +var abi = require('../solidity/abi'); +var utils = require('../utils/utils'); var eventImpl = require('./event'); var signature = require('./signature'); @@ -505,7 +1561,7 @@ function Contract(abi, address) { module.exports = contract; -},{"./abi":1,"./event":6,"./signature":14,"./utils":16,"./web3":18}],4:[function(require,module,exports){ +},{"../solidity/abi":1,"../utils/utils":5,"../web3":6,"./event":10,"./signature":19}],8:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -542,7 +1598,7 @@ module.exports = { methods: methods }; -},{}],5:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -565,81 +1621,124 @@ module.exports = { * @date 2015 */ +/** + * Web3 + * + * @module web3 + */ + +/** + * Eth methods and properties + * + * An example method object can look as follows: + * + * { + * name: 'getBlock', + * call: blockCall, + * outputFormatter: formatters.outputBlockFormatter, + * inputFormatter: [ // can be a formatter funciton or an array of functions. Where each item in the array will be used for one parameter + * utils.toHex, // formats paramter 1 + * function(param){ if(!param) return false; } // formats paramter 2 + * ] + * }, + * + * @class [web3] eth + * @constructor + */ + + var formatters = require('./formatters'); +var utils = require('../utils/utils'); var blockCall = function (args) { - return typeof args[0] === "string" ? "eth_blockByHash" : "eth_blockByNumber"; + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? "eth_getBlockByHash" : "eth_getBlockByNumber"; }; -var transactionCall = function (args) { - return typeof args[0] === "string" ? 'eth_transactionByHash' : 'eth_transactionByNumber'; +var transactionFromBlockCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getTransactionByBlockHashAndIndex' : 'eth_getTransactionByBlockNumberAndIndex'; }; var uncleCall = function (args) { - return typeof args[0] === "string" ? 'eth_uncleByHash' : 'eth_uncleByNumber'; + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getUncleByBlockHashAndIndex' : 'eth_getUncleByBlockNumberAndIndex'; }; -var transactionCountCall = function (args) { - return typeof args[0] === "string" ? 'eth_transactionCountByHash' : 'eth_transactionCountByNumber'; +var getBlockTransactionCountCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getBlockTransactionCountByHash' : 'eth_getBlockTransactionCountByNumber'; }; var uncleCountCall = function (args) { - return typeof args[0] === "string" ? 'eth_uncleCountByHash' : 'eth_uncleCountByNumber'; + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getUncleCountByBlockHash' : 'eth_getUncleCountByBlockNumber'; }; /// @returns an array of objects describing web3.eth api methods var methods = [ - { name: 'getBalance', call: 'eth_balanceAt', outputFormatter: formatters.convertToBigNumber}, - { name: 'getState', call: 'eth_stateAt' }, - { name: 'getStorage', call: 'eth_storageAt' }, - { name: 'getData', call: 'eth_codeAt' }, - { name: 'getBlock', call: blockCall, outputFormatter: formatters.outputBlockFormatter}, - { name: 'getUncle', call: uncleCall, outputFormatter: formatters.outputBlockFormatter}, - { name: 'getCompilers', call: 'eth_compilers' }, - { name: 'getBlockTransactionCount', call: transactionCountCall }, - { name: 'getBlockUncleCount', call: uncleCountCall }, - { name: 'getTransaction', call: transactionCall, outputFormatter: formatters.outputTransactionFormatter }, - { name: 'getTransactionCount', call: 'eth_countAt'}, - { name: 'sendTransaction', call: 'eth_transact', inputFormatter: formatters.inputTransactionFormatter }, - { name: 'call', call: 'eth_call' }, - { name: 'compile.solidity', call: 'eth_solidity' }, - { name: 'compile.lll', call: 'eth_lll' }, - { name: 'compile.serpent', call: 'eth_serpent' }, + { name: 'getBalance', call: 'eth_getBalance', addDefaultblock: 2, + outputFormatter: formatters.convertToBigNumber}, + { name: 'getStorage', call: 'eth_getStorage', addDefaultblock: 2}, + { name: 'getStorageAt', call: 'eth_getStorageAt', addDefaultblock: 3, + inputFormatter: utils.toHex}, + { name: 'getData', call: 'eth_getData', addDefaultblock: 2}, + { name: 'getBlock', call: blockCall, + outputFormatter: formatters.outputBlockFormatter, + inputFormatter: [utils.toHex, function(param){ return (!param) ? false : true; }]}, + { name: 'getUncle', call: uncleCall, + outputFormatter: formatters.outputBlockFormatter, + inputFormatter: [utils.toHex, utils.toHex, function(param){ return (!param) ? false : true; }]}, + { name: 'getCompilers', call: 'eth_getCompilers' }, + { name: 'getBlockTransactionCount', call: getBlockTransactionCountCall, + outputFormatter: utils.toDecimal, + inputFormatter: utils.toHex }, + { name: 'getBlockUncleCount', call: uncleCountCall, + outputFormatter: utils.toDecimal, + inputFormatter: utils.toHex }, + { name: 'getTransaction', call: 'eth_getTransactionByHash', + outputFormatter: formatters.outputTransactionFormatter }, + { name: 'getTransactionFromBlock', call: transactionFromBlockCall, + outputFormatter: formatters.outputTransactionFormatter, + inputFormatter: utils.toHex }, + { name: 'getTransactionCount', call: 'eth_getTransactionCount', addDefaultblock: 2, + outputFormatter: utils.toDecimal}, + { name: 'sendTransaction', call: 'eth_sendTransaction', + inputFormatter: formatters.inputTransactionFormatter }, + { name: 'call', call: 'eth_call', addDefaultblock: 2, + inputFormatter: formatters.inputCallFormatter }, + { name: 'compile.solidity', call: 'eth_compileSolidity', inputFormatter: utils.toHex }, + { name: 'compile.lll', call: 'eth_compileLLL', inputFormatter: utils.toHex }, + { name: 'compile.serpent', call: 'eth_compileSerpent', inputFormatter: utils.toHex }, { name: 'flush', call: 'eth_flush' }, // deprecated methods - { name: 'balanceAt', call: 'eth_balanceAt', newMethod: 'getBalance' }, - { name: 'stateAt', call: 'eth_stateAt', newMethod: 'getState' }, - { name: 'storageAt', call: 'eth_storageAt', newMethod: 'getStorage' }, - { name: 'countAt', call: 'eth_countAt', newMethod: 'getTransactionCount' }, - { name: 'codeAt', call: 'eth_codeAt', newMethod: 'getData' }, - { name: 'transact', call: 'eth_transact', newMethod: 'sendTransaction' }, - { name: 'block', call: blockCall, newMethod: 'getBlock' }, - { name: 'transaction', call: transactionCall, newMethod: 'getTransaction' }, - { name: 'uncle', call: uncleCall, newMethod: 'getUncle' }, - { name: 'compilers', call: 'eth_compilers', newMethod: 'getCompilers' }, - { name: 'solidity', call: 'eth_solidity', newMethod: 'compile.solidity' }, - { name: 'lll', call: 'eth_lll', newMethod: 'compile.lll' }, - { name: 'serpent', call: 'eth_serpent', newMethod: 'compile.serpent' }, - { name: 'transactionCount', call: transactionCountCall, newMethod: 'getBlockTransactionCount' }, - { name: 'uncleCount', call: uncleCountCall, newMethod: 'getBlockUncleCount' }, + { name: 'balanceAt', call: 'eth_balanceAt', newMethod: 'eth.getBalance' }, + { name: 'stateAt', call: 'eth_stateAt', newMethod: 'eth.getStorageAt' }, + { name: 'storageAt', call: 'eth_storageAt', newMethod: 'eth.getStorage' }, + { name: 'countAt', call: 'eth_countAt', newMethod: 'eth.getTransactionCount' }, + { name: 'codeAt', call: 'eth_codeAt', newMethod: 'eth.getData' }, + { name: 'transact', call: 'eth_transact', newMethod: 'eth.sendTransaction' }, + { name: 'block', call: blockCall, newMethod: 'eth.getBlock' }, + { name: 'transaction', call: transactionFromBlockCall, newMethod: 'eth.getTransaction' }, + { name: 'uncle', call: uncleCall, newMethod: 'eth.getUncle' }, + { name: 'compilers', call: 'eth_compilers', newMethod: 'eth.getCompilers' }, + { name: 'solidity', call: 'eth_solidity', newMethod: 'eth.compile.solidity' }, + { name: 'lll', call: 'eth_lll', newMethod: 'eth.compile.lll' }, + { name: 'serpent', call: 'eth_serpent', newMethod: 'eth.compile.serpent' }, + { name: 'transactionCount', call: getBlockTransactionCountCall, newMethod: 'eth.getBlockTransactionCount' }, + { name: 'uncleCount', call: uncleCountCall, newMethod: 'eth.getBlockUncleCount' }, { name: 'logs', call: 'eth_logs' } ]; /// @returns an array of objects describing web3.eth api properties var properties = [ - { name: 'coinbase', getter: 'eth_coinbase', setter: 'eth_setCoinbase' }, - { name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' }, - { name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' }, + { name: 'coinbase', getter: 'eth_coinbase'}, + { name: 'mining', getter: 'eth_mining'}, { name: 'gasPrice', getter: 'eth_gasPrice', outputFormatter: formatters.convertToBigNumber}, { name: 'accounts', getter: 'eth_accounts' }, - { name: 'peerCount', getter: 'eth_peerCount' }, - { name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' }, - { name: 'blockNumber', getter: 'eth_number'}, + { name: 'blockNumber', getter: 'eth_blockNumber', outputFormatter: utils.toDecimal}, // deprecated properties - { name: 'number', getter: 'eth_number', newProperty: 'blockNumber'} + { name: 'listening', getter: 'net_listening', setter: 'eth_setListening', newProperty: 'net.listening'}, + { name: 'peerCount', getter: 'net_peerCount', newProperty: 'net.peerCount'}, + { name: 'number', getter: 'eth_number', newProperty: 'eth.blockNumber'} ]; @@ -649,7 +1748,7 @@ module.exports = { }; -},{"./formatters":8}],6:[function(require,module,exports){ +},{"../utils/utils":5,"./formatters":12}],10:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -672,8 +1771,8 @@ module.exports = { * @date 2014 */ -var abi = require('./abi'); -var utils = require('./utils'); +var abi = require('../solidity/abi'); +var utils = require('../utils/utils'); var signature = require('./signature'); /// filter inputs array && returns only indexed (or not) inputs @@ -789,7 +1888,7 @@ module.exports = { }; -},{"./abi":1,"./signature":14,"./utils":16}],7:[function(require,module,exports){ +},{"../solidity/abi":1,"../utils/utils":5,"./signature":19}],11:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -815,6 +1914,8 @@ module.exports = { * @date 2014 */ +var utils = require('../utils/utils'); + /// Should be called to check if filter implementation is valid /// @returns true if it is, otherwise false var implementationIsValid = function (i) { @@ -830,25 +1931,56 @@ var implementationIsValid = function (i) { /// @param should be string or object /// @returns options string or object var getOptions = function (options) { + /*jshint maxcomplexity:5 */ + if (typeof options === 'string') { return options; } options = options || {}; - if (options.topics) { - console.warn('"topics" is deprecated, is "topic" instead'); + if (options.topic) { + console.warn('"topic" is deprecated, is "topics" instead'); + options.topics = options.topic; } + if (options.earliest) { + console.warn('"earliest" is deprecated, is "fromBlock" instead'); + options.fromBlock = options.earliest; + } + + if (options.latest) { + console.warn('"latest" is deprecated, is "toBlock" instead'); + options.toBlock = options.latest; + } + + if (options.skip) { + console.warn('"skip" is deprecated, is "offset" instead'); + options.offset = options.skip; + } + + if (options.max) { + console.warn('"max" is deprecated, is "limit" instead'); + options.limit = options.max; + } + + // make sure topics, get converted to hex + if(options.topics instanceof Array) { + options.topics = options.topics.map(function(topic){ + return utils.toHex(topic); + }); + } + + // evaluate lazy properties return { + fromBlock: utils.toHex(options.fromBlock), + toBlock: utils.toHex(options.toBlock), + limit: utils.toHex(options.limit), + offset: utils.toHex(options.offset), to: options.to, - topic: options.topic, - earliest: options.earliest, - latest: options.latest, - max: options.max, - skip: options.skip, - address: options.address + address: options.address, + topics: options.topics }; }; @@ -866,6 +1998,8 @@ var filter = function(options, implementation, formatter) { options = getOptions(options); var callbacks = []; var filterId = implementation.newFilter(options); + + // call the callbacks var onMessages = function (messages) { messages.forEach(function (message) { message = formatter ? formatter(message) : message; @@ -927,7 +2061,7 @@ var filter = function(options, implementation, formatter) { module.exports = filter; -},{}],8:[function(require,module,exports){ +},{"../utils/utils":5}],12:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -947,286 +2081,188 @@ module.exports = filter; /** @file formatters.js * @authors: * Marek Kotewicz + * Fabian Vogelsteller * @date 2015 */ -if ("build" !== 'build') {/* - var BigNumber = require('bignumber.js'); // jshint ignore:line -*/} - -var utils = require('./utils'); -var c = require('./const'); - -/// @param string string to be padded -/// @param number of characters that result string should have -/// @param sign, by default 0 -/// @returns right aligned string -var padLeft = function (string, chars, sign) { - return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; -}; - -/// Formats input value to byte representation of int -/// If value is negative, return it's two's complement -/// If the value is floating point, round it down -/// @returns right-aligned byte representation of int -var formatInputInt = function (value) { - /*jshint maxcomplexity:7 */ - var padding = c.ETH_PADDING * 2; - if (utils.isBigNumber(value) || typeof value === 'number') { - if (typeof value === 'number') - value = new BigNumber(value); - BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); - value = value.round(); - - if (value.lessThan(0)) - value = new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(value).plus(1); - value = value.toString(16); - } - else if (typeof value === 'string') { - if (value.indexOf('0x') === 0) { - value = value.substr(2); - } else { - value = formatInputInt(new BigNumber(value)); - } - } - else - value = (+value).toString(16); - return padLeft(value, padding); -}; - -/// Formats input value to byte representation of string -/// @returns left-algined byte representation of string -var formatInputString = function (value) { - return utils.fromAscii(value, c.ETH_PADDING).substr(2); -}; - -/// Formats input value to byte representation of bool -/// @returns right-aligned byte representation bool -var formatInputBool = function (value) { - return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); -}; - -/// Formats input value to byte representation of real -/// Values are multiplied by 2^m and encoded as integers -/// @returns byte representation of real -var formatInputReal = function (value) { - return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); -}; - - -/// Check if input value is negative -/// @param value is hex format -/// @returns true if it is negative, otherwise false -var signedIsNegative = function (value) { - return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; -}; - -/// Formats input right-aligned input bytes to int -/// @returns right-aligned input bytes formatted to int -var formatOutputInt = function (value) { - - value = value || "0"; - - // check if it's negative number - // it it is, return two's complement - if (signedIsNegative(value)) { - return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); - } - return new BigNumber(value, 16); -}; - - -/// Formats big right-aligned input bytes to uint -/// @returns right-aligned input bytes formatted to uint -var formatOutputUInt = function (value) { - value = value || "0"; - return new BigNumber(value, 16); -}; - -/// @returns input bytes formatted to real -var formatOutputReal = function (value) { - return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); -}; - -/// @returns input bytes formatted to ureal -var formatOutputUReal = function (value) { - return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); -}; - -/// @returns right-aligned input bytes formatted to hex -var formatOutputHash = function (value) { - return "0x" + value; -}; - -/// @returns right-aligned input bytes formatted to bool -var formatOutputBool = function (value) { - return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; -}; - -/// @returns left-aligned input bytes formatted to ascii string -var formatOutputString = function (value) { - return utils.toAscii(value); -}; - -/// @returns right-aligned input bytes formatted to address -var formatOutputAddress = function (value) { - return "0x" + value.slice(value.length - 40, value.length); -}; - - -/// Formats the input to a big number -/// @returns a BigNumber object -var convertToBigNumber = function (value) { - - // remove the leading 0x - if(typeof value === 'string') - value = value.replace('0x', ''); - - value = value || "0"; - - return new BigNumber(value, 16); -}; - +var utils = require('../utils/utils'); /** -Formats the input of a transaction and converts all values to HEX + * Should the input to a big number + * + * @method convertToBigNumber + * @param {String|Number|BigNumber} + * @returns {BigNumber} object + */ +var convertToBigNumber = function (value) { + return utils.toBigNumber(value); +}; -@returns object +/** + * Formats the input of a transaction and converts all values to HEX + * + * @method inputTransactionFormatter + * @param {Object} transaction options + * @returns object */ -var inputTransactionFormatter = function(options){ +var inputTransactionFormatter = function (options){ // make code -> data - if(options.code) { + if (options.code) { options.data = options.code; delete options.code; } - // make endowment -> value - if(options.endowment) { - options.value = options.endowment; - delete options.endowment; - } - - - // format the following options - /*jshint maxcomplexity:5 */ - ['gasPrice', 'value'].forEach(function(key){ - - // if hex or string integer - if(typeof options[key] === 'string') { - - // if not hex assume its a number string - if(options[key].indexOf('0x') === -1) - options[key] = utils.fromDecimal(options[key]); - - // if number - } else if(typeof options[key] === 'number') { - options[key] = utils.fromDecimal(options[key]); - - // if bignumber - } else if(options[key] instanceof BigNumber) { - options[key] = '0x'+ options[key].toString(16); - } + ['gasPrice', 'gas', 'value'].forEach(function(key){ + options[key] = utils.fromDecimal(options[key]); }); - // format gas to number - options.gas = Number(options.gas); - - return options; }; /** -Formats the output of a transaction to its proper values - -@returns object + * Formats the output of a transaction to its proper values + * + * @method outputTransactionFormatter + * @param {Object} transaction + * @returns {Object} transaction */ -var outputTransactionFormatter = function(tx){ - // transform to number - tx.gas = Number(tx.gas); - - // gasPrice to bignumber - if(typeof tx.gasPrice === 'string' && tx.gasPrice.indexOf('0x') === 0) - tx.gasPrice = new BigNumber(tx.gasPrice, 16); - else - tx.gasPrice = new BigNumber(tx.gasPrice.toString(10), 10); - - // value to bignumber - if(typeof tx.value === 'string' && tx.value.indexOf('0x') === 0) - tx.value = new BigNumber(tx.value, 16); - else - tx.value = new BigNumber(tx.value.toString(10), 10); - +var outputTransactionFormatter = function (tx){ + tx.gas = utils.toDecimal(tx.gas); + tx.gasPrice = utils.toBigNumber(tx.gasPrice); + tx.value = utils.toBigNumber(tx.value); return tx; }; +/** + * Formats the input of a call and converts all values to HEX + * + * @method inputCallFormatter + * @param {Object} transaction options + * @returns object +*/ +var inputCallFormatter = function (options){ + + // make code -> data + if (options.code) { + options.data = options.code; + delete options.code; + } + + return options; +}; + /** -Formats the output of a block to its proper values - -@returns object + * Formats the output of a block to its proper values + * + * @method outputBlockFormatter + * @param {Object} block object + * @returns {Object} block object */ var outputBlockFormatter = function(block){ - /*jshint maxcomplexity:7 */ // transform to number - block.gasLimit = Number(block.gasLimit); - block.gasUsed = Number(block.gasUsed); - block.size = Number(block.size); - block.timestamp = Number(block.timestamp); - block.number = Number(block.number); + block.gasLimit = utils.toDecimal(block.gasLimit); + block.gasUsed = utils.toDecimal(block.gasUsed); + block.size = utils.toDecimal(block.size); + block.timestamp = utils.toDecimal(block.timestamp); + block.number = utils.toDecimal(block.number); - // minGasPrice to bignumber - if(block.minGasPrice) { - if(typeof block.minGasPrice === 'string' && block.minGasPrice.indexOf('0x') === 0) - block.minGasPrice = new BigNumber(block.minGasPrice, 16); - else - block.minGasPrice = new BigNumber(block.minGasPrice.toString(10), 10); - } + block.minGasPrice = utils.toBigNumber(block.minGasPrice); + block.difficulty = utils.toBigNumber(block.difficulty); + block.totalDifficulty = utils.toBigNumber(block.totalDifficulty); - - // difficulty to bignumber - if(block.difficulty) { - if(typeof block.difficulty === 'string' && block.difficulty.indexOf('0x') === 0) - block.difficulty = new BigNumber(block.difficulty, 16); - else - block.difficulty = new BigNumber(block.difficulty.toString(10), 10); - } - - - // difficulty to bignumber - if(block.totalDifficulty) { - if(typeof block.totalDifficulty === 'string' && block.totalDifficulty.indexOf('0x') === 0) - block.totalDifficulty = new BigNumber(block.totalDifficulty, 16); - else - block.totalDifficulty = new BigNumber(block.totalDifficulty.toString(10), 10); + if(block.transactions instanceof Array) { + block.transactions.forEach(function(item){ + if(!utils.isString(item)) + return outputTransactionFormatter(item); + }); } return block; }; - -module.exports = { - formatInputInt: formatInputInt, - formatInputString: formatInputString, - formatInputBool: formatInputBool, - formatInputReal: formatInputReal, - formatOutputInt: formatOutputInt, - formatOutputUInt: formatOutputUInt, - formatOutputReal: formatOutputReal, - formatOutputUReal: formatOutputUReal, - formatOutputHash: formatOutputHash, - formatOutputBool: formatOutputBool, - formatOutputString: formatOutputString, - formatOutputAddress: formatOutputAddress, - convertToBigNumber: convertToBigNumber, - inputTransactionFormatter: inputTransactionFormatter, - outputTransactionFormatter: outputTransactionFormatter, - outputBlockFormatter: outputBlockFormatter +/** + * Formats the output of a log + * + * @method outputLogFormatter + * @param {Object} log object + * @returns {Object} log +*/ +var outputLogFormatter = function(log){ + log.number = utils.toDecimal(log.number); + return log; }; -},{"./const":2,"./utils":16}],9:[function(require,module,exports){ +/** + * Formats the input of a whisper post and converts all values to HEX + * + * @method inputPostFormatter + * @param {Object} transaction object + * @returns {Object} +*/ +var inputPostFormatter = function(post){ + + post.payload = utils.toHex(post.payload); + post.ttl = utils.fromDecimal(post.ttl); + post.workToProve = utils.fromDecimal(post.workToProve); + + if(!(post.topic instanceof Array)) + post.topic = [post.topic]; + + + // format the following options + post.topic = post.topic.map(function(topic){ + return utils.fromAscii(topic); + }); + + return post; +}; + +/** + * Formats the output of a received post message + * + * @method outputPostFormatter + * @param {Object} + * @returns {Object} + */ +var outputPostFormatter = function(post){ + + post.expiry = utils.toDecimal(post.expiry); + post.sent = utils.toDecimal(post.sent); + post.ttl = utils.toDecimal(post.ttl); + post.payloadRaw = post.payload; + post.payload = utils.toAscii(post.payload); + + if(post.payload.indexOf('{') === 0 || post.payload.indexOf('[') === 0) { + try { + post.payload = JSON.parse(post.payload); + } catch (e) { } + } + + // format the following options + post.topic = post.topic.map(function(topic){ + return utils.toAscii(topic); + }); + + return post; +}; + +module.exports = { + convertToBigNumber: convertToBigNumber, + inputTransactionFormatter: inputTransactionFormatter, + outputTransactionFormatter: outputTransactionFormatter, + inputCallFormatter: inputCallFormatter, + outputBlockFormatter: outputBlockFormatter, + outputLogFormatter: outputLogFormatter, + inputPostFormatter: inputPostFormatter, + outputPostFormatter: outputPostFormatter +}; + + +},{"../utils/utils":5}],13:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1243,7 +2279,7 @@ module.exports = { You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file httpsync.js +/** @file httpprovider.js * @authors: * Marek Kotewicz * Marian Oancea @@ -1254,29 +2290,50 @@ if ("build" !== 'build') {/* var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line */} -var HttpSyncProvider = function (host) { +var HttpProvider = function (host) { + this.name = 'HTTP'; this.handlers = []; this.host = host || 'http://localhost:8080'; }; -HttpSyncProvider.prototype.send = function (payload) { - //var data = formatJsonRpcObject(payload); - +HttpProvider.prototype.send = function (payload, callback) { var request = new XMLHttpRequest(); request.open('POST', this.host, false); - request.send(JSON.stringify(payload)); - var result = request.responseText; - // check request.status - if(request.status !== 200) - return; - return JSON.parse(result); + // ASYNC + if(typeof callback === 'function') { + request.onreadystatechange = function() { + if(request.readyState === 4) { + var result = ''; + try { + result = JSON.parse(request.responseText) + } catch(error) { + result = error; + } + callback(result, request.status); + } + }; + + request.open('POST', this.host, true); + request.send(JSON.stringify(payload)); + + // SYNC + } else { + request.open('POST', this.host, false); + request.send(JSON.stringify(payload)); + + // check request.status + if(request.status !== 200) + return; + return JSON.parse(request.responseText); + + } }; -module.exports = HttpSyncProvider; +module.exports = HttpProvider; -},{}],10:[function(require,module,exports){ +},{}],14:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1343,7 +2400,50 @@ module.exports = { -},{}],11:[function(require,module,exports){ +},{}],15:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file eth.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +var utils = require('../utils/utils'); + +/// @returns an array of objects describing web3.eth api methods +var methods = [ + // { name: 'getBalance', call: 'eth_balanceAt', outputFormatter: formatters.convertToBigNumber}, +]; + +/// @returns an array of objects describing web3.eth api properties +var properties = [ + { name: 'listening', getter: 'net_listening'}, + { name: 'peerCount', getter: 'net_peerCount', outputFormatter: utils.toDecimal }, +]; + + +module.exports = { + methods: methods, + properties: properties +}; + + +},{"../utils/utils":5}],16:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1378,7 +2478,7 @@ QtSyncProvider.prototype.send = function (payload) { module.exports = QtSyncProvider; -},{}],12:[function(require,module,exports){ +},{}],17:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1405,7 +2505,7 @@ module.exports = QtSyncProvider; */ var jsonrpc = require('./jsonrpc'); -var c = require('./const'); +var c = require('../utils/config'); /** * It's responsible for passing messages to providers @@ -1417,16 +2517,29 @@ var requestManager = function() { var timeout = null; var provider; - var send = function (data) { - /*jshint maxcomplexity: 6 */ + var send = function (data, callback) { + /*jshint maxcomplexity: 7 */ - // format the input before sending + // FORMAT BASED ON ONE FORMATTER function if(typeof data.inputFormatter === 'function') { - data.params = Array.prototype.map.call(data.params, function(item){ - return data.inputFormatter(item); + data.params = Array.prototype.map.call(data.params, function(item, index){ + // format everything besides the defaultblock, which is already formated + return (!data.addDefaultblock || index+1 < data.addDefaultblock) + ? data.inputFormatter(item) + : item; + }); + + // FORMAT BASED ON the input FORMATTER ARRAY + } else if(data.inputFormatter instanceof Array) { + data.params = Array.prototype.map.call(data.inputFormatter, function(formatter, index){ + // format everything besides the defaultblock, which is already formated + return (!data.addDefaultblock || index+1 < data.addDefaultblock) + ? formatter(data.params[index]) + : data.params[index]; }); } + var payload = jsonrpc.toPayload(data.method, data.params); if (!provider) { @@ -1434,17 +2547,43 @@ var requestManager = function() { return null; } - var result = provider.send(payload); + // HTTP ASYNC (only when callback is given, and it a HttpProvidor) + if(typeof callback === 'function' && provider.name === 'HTTP'){ + provider.send(payload, function(result, status){ - if (!jsonrpc.isValidResponse(result)) { - console.log(result); - if(typeof result === 'object' && result.error && result.error.message) - console.error(result.error.message); - return null; + if (!jsonrpc.isValidResponse(result)) { + if(typeof result === 'object' && result.error && result.error.message) { + console.error(result.error.message); + callback(result.error); + } else { + callback(new Error({ + status: status, + error: result, + message: 'Bad Request' + })); + } + return null; + } + + // format the output + callback(null, (typeof data.outputFormatter === 'function') ? data.outputFormatter(result.result) : result.result); + }); + + // SYNC + } else { + var result = provider.send(payload); + + if (!jsonrpc.isValidResponse(result)) { + console.log(result); + if(typeof result === 'object' && result.error && result.error.message) + console.error(result.error.message); + return null; + } + + // format the output + return (typeof data.outputFormatter === 'function') ? data.outputFormatter(result.result) : result.result; } - // format the output - return (typeof data.outputFormatter === 'function') ? data.outputFormatter(result.result) : result.result; }; var setProvider = function (p) { @@ -1481,11 +2620,13 @@ var requestManager = function() { var poll = function () { polls.forEach(function (data) { - var result = send(data.data); - if (!(result instanceof Array) || result.length === 0) { - return; - } - data.callback(result); + // send async + send(data.data, function(result){ + if (!(result instanceof Array) || result.length === 0) { + return; + } + data.callback(result); + }); }); timeout = setTimeout(poll, c.ETH_POLLING_TIMEOUT); }; @@ -1504,7 +2645,7 @@ var requestManager = function() { module.exports = requestManager; -},{"./const":2,"./jsonrpc":10}],13:[function(require,module,exports){ +},{"../utils/config":4,"./jsonrpc":14}],18:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1527,17 +2668,19 @@ module.exports = requestManager; * @date 2015 */ +var formatters = require('./formatters'); + /// @returns an array of objects describing web3.shh api methods var methods = function () { return [ - { name: 'post', call: 'shh_post' }, + { name: 'post', call: 'shh_post', inputFormatter: formatters.inputPostFormatter }, { name: 'newIdentity', call: 'shh_newIdentity' }, - { name: 'hasIdentity', call: 'shh_haveIdentity' }, + { name: 'hasIdentity', call: 'shh_hasIdentity' }, { name: 'newGroup', call: 'shh_newGroup' }, { name: 'addToGroup', call: 'shh_addToGroup' }, // deprecated - { name: 'haveIdentity', call: 'shh_haveIdentity', newMethod: 'hasIdentity' }, + { name: 'haveIdentity', call: 'shh_haveIdentity', newMethod: 'shh.hasIdentity' }, ]; }; @@ -1546,7 +2689,7 @@ module.exports = { }; -},{}],14:[function(require,module,exports){ +},{"./formatters":12}],19:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1569,8 +2712,8 @@ module.exports = { * @date 2015 */ -var web3 = require('./web3'); -var c = require('./const'); +var web3 = require('../web3'); +var c = require('../utils/config'); /// @param function name for which we want to get signature /// @returns signature of function with given name @@ -1590,402 +2733,7 @@ module.exports = { }; -},{"./const":2,"./web3":18}],15:[function(require,module,exports){ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file types.js - * @authors: - * Marek Kotewicz - * @date 2015 - */ - -var f = require('./formatters'); - -/// @param expected type prefix (string) -/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false -var prefixedType = function (prefix) { - return function (type) { - return type.indexOf(prefix) === 0; - }; -}; - -/// @param expected type name (string) -/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false -var namedType = function (name) { - return function (type) { - return name === type; - }; -}; - -/// Setups input formatters for solidity types -/// @returns an array of input formatters -var inputTypes = function () { - - return [ - { type: prefixedType('uint'), format: f.formatInputInt }, - { type: prefixedType('int'), format: f.formatInputInt }, - { type: prefixedType('hash'), format: f.formatInputInt }, - { type: prefixedType('string'), format: f.formatInputString }, - { type: prefixedType('real'), format: f.formatInputReal }, - { type: prefixedType('ureal'), format: f.formatInputReal }, - { type: namedType('address'), format: f.formatInputInt }, - { type: namedType('bool'), format: f.formatInputBool } - ]; -}; - -/// Setups output formaters for solidity types -/// @returns an array of output formatters -var outputTypes = function () { - - return [ - { type: prefixedType('uint'), format: f.formatOutputUInt }, - { type: prefixedType('int'), format: f.formatOutputInt }, - { type: prefixedType('hash'), format: f.formatOutputHash }, - { type: prefixedType('string'), format: f.formatOutputString }, - { type: prefixedType('real'), format: f.formatOutputReal }, - { type: prefixedType('ureal'), format: f.formatOutputUReal }, - { type: namedType('address'), format: f.formatOutputAddress }, - { type: namedType('bool'), format: f.formatOutputBool } - ]; -}; - -module.exports = { - prefixedType: prefixedType, - namedType: namedType, - inputTypes: inputTypes, - outputTypes: outputTypes -}; - - -},{"./formatters":8}],16:[function(require,module,exports){ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file utils.js - * @authors: - * Marek Kotewicz - * @date 2015 - */ - -var c = require('./const'); - -if ("build" !== 'build') {/* - var BigNumber = require('bignumber.js'); // jshint ignore:line -*/} - -var unitMap = { - 'wei': '1', - 'kwei': '1000', - 'ada': '1000', - 'mwei': '1000000', - 'babbage': '1000000', - 'gwei': '1000000000', - 'shannon': '1000000000', - 'szabo': '1000000000000', - 'finney': '1000000000000000', - 'ether': '1000000000000000000', - 'kether': '1000000000000000000000', - 'grand': '1000000000000000000000', - 'einstein': '1000000000000000000000', - 'mether': '1000000000000000000000000', - 'gether': '1000000000000000000000000000', - 'tether': '1000000000000000000000000000000' -}; - - -/// Finds first index of array element matching pattern -/// @param array -/// @param callback pattern -/// @returns index of element -var findIndex = function (array, callback) { - var end = false; - var i = 0; - for (; i < array.length && !end; i++) { - end = callback(array[i]); - } - return end ? i - 1 : -1; -}; - -/// @returns ascii string representation of hex value prefixed with 0x -var toAscii = function(hex) { -// Find termination - var str = ""; - var i = 0, l = hex.length; - if (hex.substring(0, 2) === '0x') { - i = 2; - } - for (; i < l; i+=2) { - var code = parseInt(hex.substr(i, 2), 16); - if (code === 0) { - break; - } - - str += String.fromCharCode(code); - } - - return str; -}; - -var toHex = function(str) { - var hex = ""; - for(var i = 0; i < str.length; i++) { - var n = str.charCodeAt(i).toString(16); - hex += n.length < 2 ? '0' + n : n; - } - - return hex; -}; - -/// @returns hex representation (prefixed by 0x) of ascii string -var fromAscii = function(str, pad) { - pad = pad === undefined ? 0 : pad; - var hex = toHex(str); - while (hex.length < pad*2) - hex += "00"; - return "0x" + hex; -}; - -/// @returns display name for function/event eg. multiply(uint256) -> multiply -var extractDisplayName = function (name) { - var length = name.indexOf('('); - return length !== -1 ? name.substr(0, length) : name; -}; - -/// @returns overloaded part of function/event name -var extractTypeName = function (name) { - /// TODO: make it invulnerable - var length = name.indexOf('('); - return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)).replace(' ', '') : ""; -}; - -/// Filters all function from input abi -/// @returns abi array with filtered objects of type 'function' -var filterFunctions = function (json) { - return json.filter(function (current) { - return current.type === 'function'; - }); -}; - -/// Filters all events form input abi -/// @returns abi array with filtered objects of type 'event' -var filterEvents = function (json) { - return json.filter(function (current) { - return current.type === 'event'; - }); -}; - -/// used to transform value/string to eth string -/// TODO: use BigNumber.js to parse int -/// TODO: add tests for it! -var toEth = function (str) { - - console.warn('This method is deprecated please use eth.fromWei(BigNumberOrNumber, unit) instead.'); - - /*jshint maxcomplexity:7 */ - var val = typeof str === "string" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str.replace(/,/g,'').replace(/ /g,'')) : str; - var unit = 0; - var units = c.ETH_UNITS; - while (val > 3000 && unit < units.length - 1) - { - val /= 1000; - unit++; - } - var s = val.toString().length < val.toFixed(2).length ? val.toString() : val.toFixed(2); - var replaceFunction = function($0, $1, $2) { - return $1 + ',' + $2; - }; - - while (true) { - var o = s; - s = s.replace(/(\d)(\d\d\d[\.\,])/, replaceFunction); - if (o === s) - break; - } - return s + ' ' + units[unit]; -}; - - -var toDecimal = function (val) { - // remove 0x and place 0, if it's required - val = val.length > 2 ? val.substring(2) : "0"; - return (new BigNumber(val, 16).toString(10)); -}; - -var fromDecimal = function (val) { - return "0x" + (new BigNumber(val).toString(16)); -}; - - -/** -Takes a number of wei and converts it to any other ether unit. - -Possible units are: - - - kwei/ada - - mwei/babbage - - gwei/shannon - - szabo - - finney - - ether - - kether/grand/einstein - - mether - - gether - - tether - -@method fromWei -@param {Number|String} number can be a number, number string or a HEX of a decimal -@param {String} unit the unit to convert to -@return {String|Object} When given a BigNumber object it returns one as well, otherwise a number -*/ -var fromWei = function(number, unit) { - /*jshint maxcomplexity: 6 */ - unit = unit.toLowerCase(); - - var isBigNumber = true; - - if(!unitMap[unit]) { - console.warn('This unit doesn\'t exists, please use the one of the following units' , unitMap); - return number; - } - - if(!number) - return number; - - if(typeof number === 'string' && number.indexOf('0x') === 0) { - isBigNumber = false; - number = new BigNumber(number, 16); - } - - if(!(number instanceof BigNumber)) { - isBigNumber = false; - number = new BigNumber(number.toString(10), 10); // toString to prevent errors, the user have to handle giving correct bignums themselves - } - - number = number.dividedBy(new BigNumber(unitMap[unit], 10)); - - return (isBigNumber) ? number : number.toString(10); -}; - -/** -Takes a number of a unit and converts it to wei. - -Possible units are: - - - kwei/ada - - mwei/babbage - - gwei/shannon - - szabo - - finney - - ether - - kether/grand/einstein - - mether - - gether - - tether - -@method toWei -@param {Number|String|BigNumber} number can be a number, number string or a HEX of a decimal -@param {String} unit the unit to convert to -@return {String|Object} When given a BigNumber object it returns one as well, otherwise a number -*/ -var toWei = function(number, unit) { - /*jshint maxcomplexity: 6 */ - unit = unit.toLowerCase(); - - var isBigNumber = true; - - if(!unitMap[unit]) { - console.warn('This unit doesn\'t exists, please use the one of the following units' , unitMap); - return number; - } - - if(!number) - return number; - - if(typeof number === 'string' && number.indexOf('0x') === 0) { - isBigNumber = false; - number = new BigNumber(number, 16); - } - - if(!(number instanceof BigNumber)) { - isBigNumber = false; - number = new BigNumber(number.toString(10), 10);// toString to prevent errors, the user have to handle giving correct bignums themselves - } - - - number = number.times(new BigNumber(unitMap[unit], 10)); - - return (isBigNumber) ? number : number.toString(10); -}; - - -/** -Checks if the given string is a valid ethereum HEX address. - -@method isAddress -@param {String} address the given HEX adress -@return {Boolean} -*/ -var isAddress = function(address) { - if(address.indexOf('0x') === 0 && address.length !== 42) - return false; - if(address.indexOf('0x') === -1 && address.length !== 40) - return false; - - return /^\w+$/.test(address); -}; - -var isBigNumber = function (value) { - return value instanceof BigNumber || - (value && value.constructor && value.constructor.name === 'BigNumber'); -}; - - -module.exports = { - findIndex: findIndex, - toDecimal: toDecimal, - fromDecimal: fromDecimal, - toAscii: toAscii, - fromAscii: fromAscii, - extractDisplayName: extractDisplayName, - extractTypeName: extractTypeName, - filterFunctions: filterFunctions, - filterEvents: filterEvents, - toEth: toEth, - toWei: toWei, - fromWei: fromWei, - isAddress: isAddress, - isBigNumber: isBigNumber -}; - - -},{"./const":2}],17:[function(require,module,exports){ +},{"../utils/config":4,"../web3":6}],20:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -2011,13 +2759,13 @@ module.exports = { /// @returns an array of objects describing web3.eth.filter api methods var eth = function () { var newFilter = function (args) { - return typeof args[0] === 'string' ? 'eth_newFilterString' : 'eth_newFilter'; + return typeof args[0] === 'string' ? 'eth_newBlockFilter' : 'eth_newFilter'; }; return [ { name: 'newFilter', call: newFilter }, { name: 'uninstallFilter', call: 'eth_uninstallFilter' }, - { name: 'getLogs', call: 'eth_filterLogs' } + { name: 'getLogs', call: 'eth_getFilterLogs' } ]; }; @@ -2036,259 +2784,16 @@ module.exports = { }; -},{}],18:[function(require,module,exports){ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file web3.js - * @authors: - * Jeffrey Wilcke - * Marek Kotewicz - * Marian Oancea - * Gav Wood - * @date 2014 - */ - -// if (process.env.NODE_ENV !== 'build') { -// var BigNumber = require('bignumber.js'); -// } - -var eth = require('./eth'); -var db = require('./db'); -var shh = require('./shh'); -var watches = require('./watches'); -var filter = require('./filter'); -var utils = require('./utils'); -var requestManager = require('./requestmanager'); - -/// @returns an array of objects describing web3 api methods -var web3Methods = function () { - return [ - { name: 'sha3', call: 'web3_sha3' } - ]; -}; - -/// creates methods in a given object based on method description on input -/// setups api calls for these methods -var setupMethods = function (obj, methods) { - methods.forEach(function (method) { - // allow for object methods 'myObject.method' - var objectMethods = method.name.split('.'), - callFunction = function () { - var args = Array.prototype.slice.call(arguments); - var call = typeof method.call === 'function' ? method.call(args) : method.call; - - // show deprecated warning - if(method.newMethod) - console.warn('This method is deprecated please use eth.'+ method.newMethod +'() instead.'); - - return web3.manager.send({ - method: call, - params: args, - outputFormatter: method.outputFormatter, - inputFormatter: method.inputFormatter - }); - }; - - if(objectMethods.length > 1) { - if(!obj[objectMethods[0]]) - obj[objectMethods[0]] = {}; - - obj[objectMethods[0]][objectMethods[1]] = callFunction; - - } else { - - obj[objectMethods[0]] = callFunction; - } - - }); -}; - -/// creates properties in a given object based on properties description on input -/// setups api calls for these properties -var setupProperties = function (obj, properties) { - properties.forEach(function (property) { - var proto = {}; - proto.get = function () { - - // show deprecated warning - if(property.newProperty) - console.warn('This property is deprecated please use eth.'+ property.newProperty +' instead.'); - - - return web3.manager.send({ - method: property.getter, - outputFormatter: property.outputFormatter - }); - }; - - if (property.setter) { - proto.set = function (val) { - - // show deprecated warning - if(property.newProperty) - console.warn('This property is deprecated please use eth.'+ property.newProperty +' instead.'); - - return web3.manager.send({ - method: property.setter, - params: [val], - inputFormatter: property.inputFormatter - }); - }; - } - - proto.enumerable = !property.newProperty; - Object.defineProperty(obj, property.name, proto); - - }); -}; - -/*jshint maxparams:4 */ -var startPolling = function (method, id, callback, uninstall) { - web3.manager.startPolling({ - method: method, - params: [id] - }, id, callback, uninstall); -}; -/*jshint maxparams:3 */ - -var stopPolling = function (id) { - web3.manager.stopPolling(id); -}; - -var ethWatch = { - startPolling: startPolling.bind(null, 'eth_changed'), - stopPolling: stopPolling -}; - -var shhWatch = { - startPolling: startPolling.bind(null, 'shh_changed'), - stopPolling: stopPolling -}; - -/// setups web3 object, and it's in-browser executed methods -var web3 = { - manager: requestManager(), - providers: {}, - - setProvider: function (provider) { - web3.manager.setProvider(provider); - }, - - /// Should be called to reset state of web3 object - /// Resets everything except manager - reset: function () { - web3.manager.reset(); - }, - - /// @returns ascii string representation of hex value prefixed with 0x - toAscii: utils.toAscii, - - /// @returns hex representation (prefixed by 0x) of ascii string - fromAscii: utils.fromAscii, - - /// @returns decimal representaton of hex value prefixed by 0x - toDecimal: utils.toDecimal, - - /// @returns hex representation (prefixed by 0x) of decimal value - fromDecimal: utils.fromDecimal, - - /// used to transform value/string to eth string - toEth: utils.toEth, - - toWei: utils.toWei, - fromWei: utils.fromWei, - isAddress: utils.isAddress, - - - /// eth object prototype - eth: { - // DEPRECATED - contractFromAbi: function (abi) { - console.warn('Initiating a contract like this is deprecated please use var MyContract = eth.contract(abi); new MyContract(address); instead.'); - - return function(addr) { - // Default to address of Config. TODO: rremove prior to genesis. - addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; - var ret = web3.eth.contract(addr, abi); - ret.address = addr; - return ret; - }; - }, - - /// @param filter may be a string, object or event - /// @param eventParams is optional, this is an object with optional event eventParams params - /// @param options is optional, this is an object with optional event options ('max'...) - /// TODO: fix it, 4 params? no way - /*jshint maxparams:4 */ - filter: function (fil, eventParams, options, formatter) { - - // if its event, treat it differently - if (fil._isEvent) - return fil(eventParams, options); - - return filter(fil, ethWatch, formatter); - }, - // DEPRECATED - watch: function (fil, eventParams, options, formatter) { - console.warn('eth.watch() is deprecated please use eth.filter() instead.'); - return this.filter(fil, eventParams, options, formatter); - } - /*jshint maxparams:3 */ - }, - - /// db object prototype - db: {}, - - /// shh object prototype - shh: { - /// @param filter may be a string, object or event - filter: function (fil) { - return filter(fil, shhWatch); - }, - // DEPRECATED - watch: function (fil) { - console.warn('shh.watch() is deprecated please use shh.filter() instead.'); - return this.filter(fil); - } - } -}; - -/// setups all api methods -setupMethods(web3, web3Methods()); -setupMethods(web3.eth, eth.methods); -setupProperties(web3.eth, eth.properties); -setupMethods(web3.db, db.methods()); -setupMethods(web3.shh, shh.methods()); -setupMethods(ethWatch, watches.eth()); -setupMethods(shhWatch, watches.shh()); - -module.exports = web3; - - -},{"./db":4,"./eth":5,"./filter":7,"./requestmanager":12,"./shh":13,"./utils":16,"./watches":17}],"web3":[function(require,module,exports){ +},{}],"web3":[function(require,module,exports){ var web3 = require('./lib/web3'); -web3.providers.HttpSyncProvider = require('./lib/httpsync'); -web3.providers.QtSyncProvider = require('./lib/qtsync'); -web3.eth.contract = require('./lib/contract'); -web3.abi = require('./lib/abi'); +web3.providers.HttpProvider = require('./lib/web3/httpprovider'); +web3.providers.QtSyncProvider = require('./lib/web3/qtsync'); +web3.eth.contract = require('./lib/web3/contract'); +web3.abi = require('./lib/solidity/abi'); module.exports = web3; -},{"./lib/abi":1,"./lib/contract":3,"./lib/httpsync":9,"./lib/qtsync":11,"./lib/web3":18}]},{},["web3"]) +},{"./lib/solidity/abi":1,"./lib/web3":6,"./lib/web3/contract":7,"./lib/web3/httpprovider":13,"./lib/web3/qtsync":16}]},{},["web3"]) //# sourceMappingURL=ethereum.js.map diff --git a/cmd/mist/assets/qml/main.qml b/cmd/mist/assets/qml/main.qml index 6824d2ba92..8558ebd51e 100644 --- a/cmd/mist/assets/qml/main.qml +++ b/cmd/mist/assets/qml/main.qml @@ -190,17 +190,15 @@ ApplicationWindow { } } + MenuItem { + text: "Generate key" + shortcut: "Ctrl+k" + onTriggered: gui.generateKey() + } } Menu { title: "Developer" - MenuItem { - iconSource: "../icecream.png" - text: "Debugger" - shortcut: "Ctrl+d" - onTriggered: eth.startDebugger() - } - MenuItem { text: "Import Tx" onTriggered: { @@ -756,24 +754,6 @@ ApplicationWindow { } } - Rectangle { - height: 55 - color: "transparent" - visible: true - Text { - text: "DEBUG" - font.family: sourceSansPro.name - font.weight: Font.DemiBold - anchors { - left: parent.left - top: parent.verticalCenter - leftMargin: 16 - } - color: "#AAA0A0" - } - } - - ColumnLayout { id: menuLegacy visible: true diff --git a/cmd/mist/assets/qml/views/chain.qml b/cmd/mist/assets/qml/views/chain.qml index 4d1bc0e03d..9892beddf1 100644 --- a/cmd/mist/assets/qml/views/chain.qml +++ b/cmd/mist/assets/qml/views/chain.qml @@ -178,7 +178,6 @@ Rectangle { } function showContractData(tx) { - txDetailsDebugButton.tx = tx if(tx.createsContract) { contractData.text = tx.data contractLabel.text = "

Transaction created contract " + tx.address + "

" @@ -202,22 +201,6 @@ Rectangle { id: contractLabel anchors.leftMargin: 10 } - Button { - property var tx - id: txDetailsDebugButton - anchors.right: parent.right - anchors.rightMargin: 10 - anchors.top: parent.top - anchors.topMargin: 10 - text: "Debug contract" - onClicked: { - if(tx && tx.createsContract){ - eth.startDbWithCode(tx.rawData) - }else { - eth.startDbWithContractAndData(tx.address, tx.rawData) - } - } - } TextArea { id: contractData text: "Contract" diff --git a/cmd/mist/assets/qml/views/info.qml b/cmd/mist/assets/qml/views/info.qml index b2d2f521c9..0187bba6d6 100644 --- a/cmd/mist/assets/qml/views/info.qml +++ b/cmd/mist/assets/qml/views/info.qml @@ -54,7 +54,6 @@ Rectangle { height: 200 anchors { left: parent.left - right: logLevelSlider.left bottom: parent.bottom top: parent.top } @@ -107,46 +106,6 @@ Rectangle { } } } - - /* - TableView { - id: logView - headerVisible: false - anchors { - right: logLevelSlider.left - left: parent.left - bottom: parent.bottom - top: parent.top - } - - TableViewColumn{ role: "description" ; title: "log" } - - model: logModel - } - */ - - Slider { - id: logLevelSlider - value: gui.getLogLevelInt() - anchors { - right: parent.right - top: parent.top - bottom: parent.bottom - - rightMargin: 5 - leftMargin: 5 - topMargin: 5 - bottomMargin: 5 - } - - orientation: Qt.Vertical - maximumValue: 5 - stepSize: 1 - - onValueChanged: { - gui.setLogLevel(value) - } - } } property var logModel: ListModel { diff --git a/cmd/mist/bindings.go b/cmd/mist/bindings.go index c63f115918..b56c0dddf5 100644 --- a/cmd/mist/bindings.go +++ b/cmd/mist/bindings.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/state" ) @@ -37,19 +36,7 @@ type plugin struct { Path string `json:"path"` } -// LogPrint writes to the GUI log. -func (gui *Gui) LogPrint(level logger.LogLevel, msg string) { - /* - str := strings.TrimRight(s, "\n") - lines := strings.Split(str, "\n") - - view := gui.getObjectByName("infoView") - for _, line := range lines { - view.Call("addLog", line) - } - */ -} -func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (string, error) { +func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (string, error) { var data string if len(recipient) == 0 { code, err := ethutil.Compile(d, false) @@ -61,18 +48,7 @@ func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (string, err data = ethutil.Bytes2Hex(utils.FormatTransactionData(d)) } - return gui.xeth.Transact(recipient, value, gas, gasPrice, data) -} - -// functions that allow Gui to implement interface guilogger.LogSystem -func (gui *Gui) SetLogLevel(level logger.LogLevel) { - gui.logLevel = level - gui.eth.Logger().SetLogLevel(level) - gui.config.Save("loglevel", level) -} - -func (gui *Gui) GetLogLevel() logger.LogLevel { - return gui.logLevel + return gui.xeth.Transact(from, recipient, value, gas, gasPrice, data) } func (self *Gui) AddPlugin(pluginPath string) { @@ -89,11 +65,6 @@ func (self *Gui) RemovePlugin(pluginPath string) { ethutil.WriteFile(self.eth.DataDir+"/plugins.json", json) } -// this extra function needed to give int typecast value to gui widget -// that sets initial loglevel to default -func (gui *Gui) GetLogLevelInt() int { - return int(gui.logLevel) -} func (self *Gui) DumpState(hash, path string) { var stateDump []byte diff --git a/cmd/mist/debugger.go b/cmd/mist/debugger.go deleted file mode 100644 index c1ab2f3f18..0000000000 --- a/cmd/mist/debugger.go +++ /dev/null @@ -1,367 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - go-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with go-ethereum. If not, see . -*/ -/** - * @authors - * Jeffrey Wilcke - */ -package main - -import ( - "fmt" - "math/big" - "strconv" - "strings" - "unicode" - - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/state" - "github.com/ethereum/go-ethereum/vm" - "github.com/obscuren/qml" -) - -type DebuggerWindow struct { - win *qml.Window - engine *qml.Engine - lib *UiLib - - vm *vm.Vm - Db *Debugger - - state *state.StateDB -} - -func NewDebuggerWindow(lib *UiLib) *DebuggerWindow { - engine := qml.NewEngine() - component, err := engine.LoadFile(lib.AssetPath("debugger/debugger.qml")) - if err != nil { - fmt.Println(err) - - return nil - } - - win := component.CreateWindow(nil) - - w := &DebuggerWindow{engine: engine, win: win, lib: lib, vm: &vm.Vm{}} - w.Db = NewDebugger(w) - - return w -} - -func (self *DebuggerWindow) Show() { - context := self.engine.Context() - context.SetVar("dbg", self) - - go func() { - self.win.Show() - self.win.Wait() - }() -} - -func (self *DebuggerWindow) SetCode(code string) { - self.win.Set("codeText", code) -} - -func (self *DebuggerWindow) SetData(data string) { - self.win.Set("dataText", data) -} - -func (self *DebuggerWindow) SetAsm(data []byte) { - self.win.Root().Call("clearAsm") - - dis := core.Disassemble(data) - for _, str := range dis { - self.win.Root().Call("setAsm", str) - } -} - -func (self *DebuggerWindow) Compile(code string) { - var err error - script := ethutil.StringToByteFunc(code, func(s string) (ret []byte) { - ret, err = ethutil.Compile(s, true) - return - }) - - if err == nil { - self.SetAsm(script) - } -} - -// Used by QML -func (self *DebuggerWindow) AutoComp(code string) { - if self.Db.done { - self.Compile(code) - } -} - -func (self *DebuggerWindow) ClearLog() { - self.win.Root().Call("clearLog") -} - -func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, dataStr string) { - self.Stop() - - defer func() { - if r := recover(); r != nil { - self.Logf("compile FAULT: %v", r) - } - }() - - data := utils.FormatTransactionData(dataStr) - - var err error - script := ethutil.StringToByteFunc(scriptStr, func(s string) (ret []byte) { - ret, err = ethutil.Compile(s, false) - return - }) - - if err != nil { - self.Logln(err) - - return - } - - var ( - gas = ethutil.Big(gasStr) - gasPrice = ethutil.Big(gasPriceStr) - value = ethutil.Big(valueStr) - // Contract addr as test address - keyPair = self.lib.eth.KeyManager().KeyPair() - ) - - statedb := self.lib.eth.ChainManager().TransState() - account := self.lib.eth.ChainManager().TransState().GetAccount(keyPair.Address()) - contract := statedb.NewStateObject([]byte{0}) - contract.SetCode(script) - contract.SetBalance(value) - - self.SetAsm(script) - - block := self.lib.eth.ChainManager().CurrentBlock() - - msg := types.NewTransactionMessage(nil, value, gas, gasPrice, data) - env := core.NewEnv(statedb, self.lib.eth.ChainManager(), msg, block) - - self.Logf("callsize %d", len(script)) - go func() { - pgas := new(big.Int).Set(gas) - ret, err := env.Call(account, contract.Address(), data, gas, gasPrice, ethutil.Big0) - - rgas := new(big.Int).Sub(pgas, gas) - tot := new(big.Int).Mul(rgas, gasPrice) - self.Logf("gas usage %v total price = %v (%v)", rgas, tot, ethutil.CurrencyToString(tot)) - if err != nil { - self.Logln("exited with errors:", err) - } else { - if len(ret) > 0 { - self.Logf("exited: % x", ret) - } else { - self.Logf("exited: nil") - } - } - - statedb.Reset() - - if !self.Db.interrupt { - self.Db.done = true - } else { - self.Db.interrupt = false - } - }() -} - -func (self *DebuggerWindow) Logf(format string, v ...interface{}) { - self.win.Root().Call("setLog", fmt.Sprintf(format, v...)) -} - -func (self *DebuggerWindow) Logln(v ...interface{}) { - str := fmt.Sprintln(v...) - self.Logf("%s", str[:len(str)-1]) -} - -func (self *DebuggerWindow) Next() { - self.Db.Next() -} - -func (self *DebuggerWindow) Continue() { - self.vm.Stepping = false - self.Next() -} - -func (self *DebuggerWindow) Stop() { - if !self.Db.done { - self.Db.Q <- true - } -} - -func (self *DebuggerWindow) ExecCommand(command string) { - if len(command) > 0 { - cmd := strings.Split(command, " ") - switch cmd[0] { - case "help": - self.Logln("Debugger commands:") - self.Logln("break, bp Set breakpoint on instruction") - self.Logln("clear [log, break, bp] Clears previous set sub-command(s)") - case "break", "bp": - if len(cmd) > 1 { - lineNo, err := strconv.Atoi(cmd[1]) - if err != nil { - self.Logln(err) - break - } - self.Db.breakPoints = append(self.Db.breakPoints, int64(lineNo)) - self.Logf("break point set on instruction %d", lineNo) - } else { - self.Logf("'%s' requires line number", cmd[0]) - } - case "clear": - if len(cmd) > 1 { - switch cmd[1] { - case "break", "bp": - self.Db.breakPoints = nil - - self.Logln("Breakpoints cleared") - case "log": - self.ClearLog() - default: - self.Logf("clear '%s' is not valid", cmd[1]) - } - } else { - self.Logln("'clear' requires sub command") - } - - default: - self.Logf("Unknown command %s", cmd[0]) - } - } -} - -type Debugger struct { - N chan bool - Q chan bool - done, interrupt bool - breakPoints []int64 - main *DebuggerWindow - win *qml.Window -} - -func NewDebugger(main *DebuggerWindow) *Debugger { - db := &Debugger{make(chan bool), make(chan bool), true, false, nil, main, main.win} - - return db -} - -type storeVal struct { - Key, Value string -} - -func (self *Debugger) Step(evm vm.VirtualMachine, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, context *vm.Context) { -} - -func (self *Debugger) BreakHook(pc int, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, stateObject *state.StateObject) bool { - self.main.Logln("break on instr:", pc) - - return self.halting(pc, op, mem, stack, stateObject) -} - -func (self *Debugger) StepHook(pc int, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, stateObject *state.StateObject) bool { - return self.halting(pc, op, mem, stack, stateObject) -} - -func (self *Debugger) SetCode(byteCode []byte) { - self.main.SetAsm(byteCode) -} - -func (self *Debugger) BreakPoints() []int64 { - return self.breakPoints -} - -func (d *Debugger) halting(pc int, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, stateObject *state.StateObject) bool { - d.win.Root().Call("setInstruction", pc) - d.win.Root().Call("clearMem") - d.win.Root().Call("clearStack") - d.win.Root().Call("clearStorage") - - addr := 0 - for i := 0; i+16 <= mem.Len(); i += 16 { - dat := mem.Data()[i : i+16] - var str string - - for _, d := range dat { - if unicode.IsGraphic(rune(d)) { - str += string(d) - } else { - str += "?" - } - } - - d.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("%s % x", str, dat)}) - addr += 16 - } - - for _, val := range stack.Data() { - d.win.Root().Call("setStack", val.String()) - } - - it := stateObject.Trie().Iterator() - for it.Next() { - d.win.Root().Call("setStorage", storeVal{fmt.Sprintf("% x", it.Key), fmt.Sprintf("% x", it.Value)}) - - } - - stackFrameAt := new(big.Int).SetBytes(mem.Get(0, 32)) - psize := mem.Len() - int(new(big.Int).SetBytes(mem.Get(0, 32)).Uint64()) - d.win.Root().ObjectByName("stackFrame").Set("text", fmt.Sprintf(`stack ptr: %v`, stackFrameAt)) - d.win.Root().ObjectByName("stackSize").Set("text", fmt.Sprintf(`stack size: %d`, psize)) - d.win.Root().ObjectByName("memSize").Set("text", fmt.Sprintf(`mem size: %v`, mem.Len())) - -out: - for { - select { - case <-d.N: - break out - case <-d.Q: - d.interrupt = true - d.clearBuffers() - - return false - } - } - - return true -} - -func (d *Debugger) clearBuffers() { -out: - // drain - for { - select { - case <-d.N: - case <-d.Q: - default: - break out - } - } -} - -func (d *Debugger) Next() { - if !d.done { - d.N <- true - } -} diff --git a/cmd/mist/flags.go b/cmd/mist/flags.go deleted file mode 100644 index 139af5923b..0000000000 --- a/cmd/mist/flags.go +++ /dev/null @@ -1,130 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - go-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with go-ethereum. If not, see . -*/ -/** - * @authors - * Jeffrey Wilcke - */ -package main - -import ( - "crypto/ecdsa" - "flag" - "fmt" - "log" - "os" - "path" - "runtime" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/vm" -) - -var ( - Identifier string - KeyRing string - KeyStore string - StartRpc bool - RpcListenAddress string - RpcPort int - OutboundPort string - ShowGenesis bool - AddPeer string - MaxPeer int - GenAddr bool - BootNodes string - NodeKey *ecdsa.PrivateKey - NAT nat.Interface - SecretFile string - ExportDir string - NonInteractive bool - Datadir string - LogFile string - ConfigFile string - DebugFile string - LogLevel int - VmType int - MinerThreads int -) - -// flags specific to gui client -var AssetPath string -var defaultConfigFile = path.Join(ethutil.DefaultDataDir(), "conf.ini") - -func Init() { - // TODO: move common flag processing to cmd/utils - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s [options] [filename]:\noptions precedence: default < config file < environment variables < command line\n", os.Args[0]) - flag.PrintDefaults() - } - - flag.IntVar(&VmType, "vm", 0, "Virtual Machine type: 0-1: standard, debug") - flag.StringVar(&Identifier, "id", "", "Custom client identifier") - flag.StringVar(&KeyRing, "keyring", "", "identifier for keyring to use") - flag.StringVar(&KeyStore, "keystore", "db", "system to store keyrings: db|file") - flag.StringVar(&RpcListenAddress, "rpcaddr", "127.0.0.1", "address for json-rpc server to listen on") - flag.IntVar(&RpcPort, "rpcport", 8545, "port to start json-rpc server on") - flag.BoolVar(&StartRpc, "rpc", true, "start rpc server") - flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)") - flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") - flag.StringVar(&SecretFile, "import", "", "imports the file given (hex or mnemonic formats)") - flag.StringVar(&ExportDir, "export", "", "exports the session keyring to files in the directory given") - flag.StringVar(&LogFile, "logfile", "", "log file (defaults to standard output)") - flag.StringVar(&Datadir, "datadir", ethutil.DefaultDataDir(), "specifies the datadir to use") - flag.StringVar(&ConfigFile, "conf", defaultConfigFile, "config file") - flag.StringVar(&DebugFile, "debug", "", "debug file (no debugging if not set)") - flag.IntVar(&LogLevel, "loglevel", int(logger.InfoLevel), "loglevel: 0-5 (= silent,error,warn,info,debug,debug detail)") - - flag.StringVar(&AssetPath, "asset_path", ethutil.DefaultAssetPath(), "absolute path to GUI assets directory") - - // Network stuff - var ( - nodeKeyFile = flag.String("nodekey", "", "network private key file") - nodeKeyHex = flag.String("nodekeyhex", "", "network private key (for testing)") - natstr = flag.String("nat", "any", "port mapping mechanism (any|none|upnp|pmp|extip:)") - ) - flag.StringVar(&OutboundPort, "port", "30303", "listening port") - flag.StringVar(&BootNodes, "bootnodes", "", "space-separated node URLs for discovery bootstrap") - flag.IntVar(&MaxPeer, "maxpeer", 30, "maximum desired peers") - - flag.IntVar(&MinerThreads, "minerthreads", runtime.NumCPU(), "number of miner threads") - - flag.Parse() - - var err error - if NAT, err = nat.Parse(*natstr); err != nil { - log.Fatalf("-nat: %v", err) - } - switch { - case *nodeKeyFile != "" && *nodeKeyHex != "": - log.Fatal("Options -nodekey and -nodekeyhex are mutually exclusive") - case *nodeKeyFile != "": - if NodeKey, err = crypto.LoadECDSA(*nodeKeyFile); err != nil { - log.Fatalf("-nodekey: %v", err) - } - case *nodeKeyHex != "": - if NodeKey, err = crypto.HexToECDSA(*nodeKeyHex); err != nil { - log.Fatalf("-nodekeyhex: %v", err) - } - } - - if VmType >= int(vm.MaxVmTy) { - log.Fatal("Invalid VM type ", VmType) - } -} diff --git a/cmd/mist/gui.go b/cmd/mist/gui.go index 53ca35574e..a49e9f6f82 100644 --- a/cmd/mist/gui.go +++ b/cmd/mist/gui.go @@ -23,7 +23,6 @@ package main import "C" import ( - "bytes" "encoding/json" "fmt" "io/ioutil" @@ -70,20 +69,18 @@ type Gui struct { txDb *ethdb.LDBDatabase - logLevel logger.LogLevel - open bool + open bool xeth *xeth.XEth Session string - config *ethutil.ConfigManager plugins map[string]plugin } // Create GUI, but doesn't start it -func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session string, logLevel int) *Gui { - db, err := ethdb.NewLDBDatabase("tx_database") +func NewWindow(ethereum *eth.Ethereum) *Gui { + db, err := ethdb.NewLDBDatabase(path.Join(ethereum.DataDir, "tx_database")) if err != nil { panic(err) } @@ -92,10 +89,7 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session st gui := &Gui{eth: ethereum, txDb: db, xeth: xeth, - logLevel: logger.LogLevel(logLevel), - Session: session, open: false, - config: config, plugins: make(map[string]plugin), serviceEvents: make(chan ServEv, 1), } @@ -142,18 +136,12 @@ func (gui *Gui) Start(assetPath string) { gui.open = true win.Show() - // only add the gui guilogger after window is shown otherwise slider wont be shown - logger.AddLogSystem(gui) win.Wait() - - // need to silence gui guilogger after window closed otherwise logsystem hangs (but do not save loglevel) - gui.logLevel = logger.Silence gui.open = false } func (gui *Gui) Stop() { if gui.open { - gui.logLevel = logger.Silence gui.open = false gui.win.Hide() } @@ -172,7 +160,11 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { return gui.win, nil } -func (gui *Gui) ImportKey(filePath string) { +func (gui *Gui) GenerateKey() { + _, err := gui.eth.AccountManager().NewAccount("hurr") + if err != nil { + // TODO: UI feedback? + } } func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) { @@ -191,31 +183,11 @@ func (gui *Gui) createWindow(comp qml.Object) *qml.Window { return gui.win } -func (gui *Gui) ImportAndSetPrivKey(secret string) bool { - err := gui.eth.KeyManager().InitFromString(gui.Session, 0, secret) - if err != nil { - guilogger.Errorln("unable to import: ", err) - return false - } - guilogger.Errorln("successfully imported: ", err) - return true -} - -func (gui *Gui) CreateAndSetPrivKey() (string, string, string, string) { - err := gui.eth.KeyManager().Init(gui.Session, 0, true) - if err != nil { - guilogger.Errorln("unable to create key: ", err) - return "", "", "", "" - } - return gui.eth.KeyManager().KeyPair().AsStrings() -} - func (gui *Gui) setInitialChain(ancientBlocks bool) { sBlk := gui.eth.ChainManager().LastBlockHash() blk := gui.eth.ChainManager().GetBlock(sBlk) for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) { sBlk = blk.ParentHash() - gui.processBlock(blk, true) } } @@ -259,10 +231,8 @@ func (self *Gui) loadMergedMiningOptions() { } func (gui *Gui) insertTransaction(window string, tx *types.Transaction) { - addr := gui.address() - var inout string - if bytes.Compare(tx.From(), addr) == 0 { + if gui.eth.AccountManager().HasAccount(tx.From()) { inout = "send" } else { inout = "recv" @@ -480,14 +450,6 @@ func (gui *Gui) setPeerInfo() { } } -func (gui *Gui) privateKey() string { - return ethutil.Bytes2Hex(gui.eth.KeyManager().PrivateKey()) -} - -func (gui *Gui) address() []byte { - return gui.eth.KeyManager().Address() -} - /* func LoadExtension(path string) (uintptr, error) { lib, err := ffi.NewLibrary(path) diff --git a/cmd/mist/main.go b/cmd/mist/main.go index 7d78c9c025..9a773e33a8 100644 --- a/cmd/mist/main.go +++ b/cmd/mist/main.go @@ -26,10 +26,10 @@ import ( "runtime" "time" + "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/ui/qt/webengine" "github.com/obscuren/qml" ) @@ -39,56 +39,32 @@ const ( Version = "0.9.0" ) -var ethereum *eth.Ethereum -var mainlogger = logger.NewLogger("MAIN") - -func run() error { - webengine.Initialize() - - // precedence: code-internal flag default < config file < environment variables < command line - Init() // parsing command line - - tstart := time.Now() - config := utils.InitConfig(VmType, ConfigFile, Datadir, "ETH") - - ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(ClientIdentifier, Version), - KeyStore: KeyStore, - DataDir: Datadir, - LogFile: LogFile, - LogLevel: LogLevel, - MaxPeers: MaxPeer, - Port: OutboundPort, - NAT: NAT, - Shh: true, - BootNodes: BootNodes, - NodeKey: NodeKey, - KeyRing: KeyRing, - Dial: true, - MinerThreads: MinerThreads, - }) - if err != nil { - mainlogger.Fatalln(err) +var ( + app = utils.NewApp(Version, "the ether browser") + assetPathFlag = cli.StringFlag{ + Name: "asset_path", + Usage: "absolute path to GUI assets directory", + Value: ethutil.DefaultAssetPath(), } - utils.KeyTasks(ethereum.KeyManager(), KeyRing, GenAddr, SecretFile, ExportDir, NonInteractive) +) - if StartRpc { - utils.StartRpc(ethereum, RpcListenAddress, RpcPort) +func init() { + app.Action = run + app.Flags = []cli.Flag{ + assetPathFlag, + + utils.BootnodesFlag, + utils.DataDirFlag, + utils.ListenPortFlag, + utils.LogFileFlag, + utils.LogLevelFlag, + utils.MaxPeersFlag, + utils.MinerThreadsFlag, + utils.NATFlag, + utils.NodeKeyFileFlag, + utils.RPCListenAddrFlag, + utils.RPCPortFlag, } - - gui := NewWindow(ethereum, config, KeyRing, LogLevel) - - utils.RegisterInterrupt(func(os.Signal) { - gui.Stop() - }) - go utils.StartEthereum(ethereum) - - fmt.Println("ETH stack took", time.Since(tstart)) - - // gui blocks the main thread - gui.Start(AssetPath) - - return nil } func main() { @@ -97,15 +73,16 @@ func main() { // This is a bit of a cheat, but ey! os.Setenv("QTWEBKIT_INSPECTOR_SERVER", "127.0.0.1:99999") - qml.Run(run) - var interrupted = false utils.RegisterInterrupt(func(os.Signal) { interrupted = true }) - utils.HandleInterrupt() + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, "Error: ", err) + } + // we need to run the interrupt callbacks in case gui is closed // this skips if we got here by actual interrupt stopping the GUI if !interrupted { @@ -113,3 +90,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 + }) +} diff --git a/cmd/mist/ui_lib.go b/cmd/mist/ui_lib.go index 187d5b2d60..4198c6316d 100644 --- a/cmd/mist/ui_lib.go +++ b/cmd/mist/ui_lib.go @@ -47,9 +47,7 @@ type UiLib struct { connected bool assetPath string // The main application window - win *qml.Window - Db *Debugger - DbWindow *DebuggerWindow + win *qml.Window jsEngine *javascript.JSRE @@ -126,33 +124,11 @@ func (ui *UiLib) AssetPath(p string) string { return path.Join(ui.assetPath, p) } -func (self *UiLib) StartDbWithContractAndData(contractHash, data string) { - dbWindow := NewDebuggerWindow(self) - object := self.eth.ChainManager().State().GetStateObject(ethutil.Hex2Bytes(contractHash)) - if len(object.Code()) > 0 { - dbWindow.SetCode(ethutil.Bytes2Hex(object.Code())) - } - dbWindow.SetData(data) - - dbWindow.Show() -} - -func (self *UiLib) StartDbWithCode(code string) { - dbWindow := NewDebuggerWindow(self) - dbWindow.SetCode(code) - dbWindow.Show() -} - -func (self *UiLib) StartDebugger() { - dbWindow := NewDebuggerWindow(self) - - dbWindow.Show() -} - func (self *UiLib) Transact(params map[string]interface{}) (string, error) { object := mapToTxParams(params) return self.XEth.Transact( + object["from"], object["to"], object["value"], object["gas"], diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 4188802ac2..6b99b841f9 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -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() @@ -133,45 +122,13 @@ 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 StartEthereumForTest(ethereum *eth.Ethereum) { + clilogger.Infoln("Starting ", ethereum.Name()) + ethereum.StartForTest() + RegisterInterrupt(func(sig os.Signal) { + ethereum.Stop() + logger.Flush() + }) } func FormatTransactionData(data string) []byte { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 88ff3558d9..6372aaa423 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2,10 +2,15 @@ package utils import ( "crypto/ecdsa" + "fmt" + "net" + "net/http" + "os" "path" "runtime" "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -15,8 +20,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(ð.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(ð.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,22 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.D } return core.NewChainManager(blockDb, stateDb, new(event.TypeMux)), blockDb, stateDb } + +func GetAccountManager(ctx *cli.Context) *accounts.Manager { + dataDir := ctx.GlobalString(DataDirFlag.Name) + ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) + 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)) +} diff --git a/core/block_processor.go b/core/block_processor.go index 34c12729cd..7ac8a1bd20 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -80,7 +80,7 @@ func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, stated cb := statedb.GetStateObject(coinbase.Address()) st := NewStateTransition(NewEnv(statedb, self.bc, tx, block), tx, cb) _, err := st.TransitionState() - if err != nil && (IsNonceErr(err) || state.IsGasLimitErr(err)) { + if err != nil && (IsNonceErr(err) || state.IsGasLimitErr(err) || IsInvalidTxErr(err)) { return nil, nil, err } @@ -120,17 +120,12 @@ func (self *BlockProcessor) ApplyTransactions(coinbase *state.StateObject, state for _, tx := range txs { receipt, txGas, err := self.ApplyTransaction(coinbase, statedb, block, tx, totalUsedGas, transientProcess) + if err != nil && (IsNonceErr(err) || state.IsGasLimitErr(err) || IsInvalidTxErr(err)) { + return nil, nil, nil, nil, err + } + if err != nil { - switch { - case IsNonceErr(err): - return nil, nil, nil, nil, err - case state.IsGasLimitErr(err): - return nil, nil, nil, nil, err - default: - statelogger.Infoln(err) - erroneous = append(erroneous, tx) - err = nil - } + statelogger.Infoln("TX err:", err) } receipts = append(receipts, receipt) handled = append(handled, tx) @@ -261,7 +256,7 @@ func (sm *BlockProcessor) ValidateHeader(block, parent *types.Header) error { } if block.Time <= parent.Time { - return ValidationError("Block timestamp not after or equal to prev block (%v - %v)", block.Time, parent.Time) + return ValidationError("Block timestamp equal or less than previous block (%v - %v)", block.Time, parent.Time) } if int64(block.Time) > time.Now().Unix() { diff --git a/core/chain_manager.go b/core/chain_manager.go index 9dc41f421d..97c61395ea 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -440,12 +440,14 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error { self.setTotalDifficulty(td) self.insert(block) + /* XXX crashes jsonlogger.LogJson(&logger.EthChainNewHead{ BlockHash: ethutil.Bytes2Hex(block.Hash()), BlockNumber: block.Number(), ChainHeadHash: ethutil.Bytes2Hex(cblock.Hash()), BlockPrevHash: ethutil.Bytes2Hex(block.ParentHash()), }) + */ self.setTransState(state.New(block.Root(), self.stateDb)) queue[i] = ChainEvent{block} diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go index e78c2e980e..91822f9e20 100644 --- a/core/chain_manager_test.go +++ b/core/chain_manager_test.go @@ -12,14 +12,12 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rlp" ) func init() { runtime.GOMAXPROCS(runtime.NumCPU()) - ethutil.ReadConfig("/tmp/ethtest", "/tmp/ethtest", "ETH") } // Test fork of length N starting from block i diff --git a/core/helper_test.go b/core/helper_test.go index 473576e3f1..36d9e70a83 100644 --- a/core/helper_test.go +++ b/core/helper_test.go @@ -62,8 +62,6 @@ func (tm *TestManager) Db() ethutil.Database { } func NewTestManager() *TestManager { - ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "ETH") - db, err := ethdb.NewMemDatabase() if err != nil { fmt.Println("Could not create mem-db, failing") diff --git a/core/manager.go b/core/manager.go index 803069377a..c4052cc058 100644 --- a/core/manager.go +++ b/core/manager.go @@ -1,7 +1,6 @@ package core import ( - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" @@ -14,7 +13,6 @@ type Backend interface { PeerCount() int IsListening() bool Peers() []*p2p.Peer - KeyManager() *crypto.KeyManager BlockDb() ethutil.Database StateDb() ethutil.Database EventMux() *event.TypeMux diff --git a/core/state_transition.go b/core/state_transition.go index f54acd6eec..7659e3d50a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -45,8 +45,6 @@ type StateTransition struct { } type Message interface { - Hash() []byte - From() []byte To() []byte @@ -153,7 +151,7 @@ func (self *StateTransition) preCheck() (err error) { } func (self *StateTransition) TransitionState() (ret []byte, err error) { - statelogger.Debugf("(~) %x\n", self.msg.Hash()) + // statelogger.Debugf("(~) %x\n", self.msg.Hash()) // XXX Transactions after this point are considered valid. if err = self.preCheck(); err != nil { @@ -186,6 +184,7 @@ func (self *StateTransition) TransitionState() (ret []byte, err error) { } } if err = self.UseGas(big.NewInt(dgas)); err != nil { + println("2") return nil, InvalidTxError(err) } @@ -200,48 +199,18 @@ func (self *StateTransition) TransitionState() (ret []byte, err error) { dataGas.Mul(dataGas, vm.GasCreateByte) if err := self.UseGas(dataGas); err == nil { ref.SetCode(ret) + } else { + statelogger.Infoln("Insufficient gas for creating code. Require", dataGas, "and have", self.gas) } } - - /* - if vmenv, ok := vmenv.(*VMEnv); ok && tryJit { - statelogger.Infof("CREATE: re-running using JIT (PH=%x)\n", stateCopy.Root()[:4]) - // re-run using the JIT (validation for the JIT) - goodState := vmenv.State().Copy() - vmenv.state = stateCopy - vmenv.SetVmType(vm.JitVmTy) - vmenv.Create(sender, contract.Address(), self.msg.Data(), self.gas, self.gasPrice, self.value) - statelogger.Infof("DONE PH=%x STD_H=%x JIT_H=%x\n", stateCopy.Root()[:4], goodState.Root()[:4], vmenv.State().Root()[:4]) - self.state.Set(goodState) - } - */ } else { ret, err = vmenv.Call(self.From(), self.To().Address(), self.msg.Data(), self.gas, self.gasPrice, self.value) - - /* - if vmenv, ok := vmenv.(*VMEnv); ok && tryJit { - statelogger.Infof("CALL: re-running using JIT (PH=%x)\n", stateCopy.Root()[:4]) - // re-run using the JIT (validation for the JIT) - goodState := vmenv.State().Copy() - vmenv.state = stateCopy - vmenv.SetVmType(vm.JitVmTy) - vmenv.Call(self.From(), self.To().Address(), self.msg.Data(), self.gas, self.gasPrice, self.value) - statelogger.Infof("DONE PH=%x STD_H=%x JIT_H=%x\n", stateCopy.Root()[:4], goodState.Root()[:4], vmenv.State().Root()[:4]) - self.state.Set(goodState) - } - */ } if err != nil && IsValueTransferErr(err) { return nil, InvalidTxError(err) } - /* - if err != nil { - self.UseGas(self.gas) - } - */ - return } diff --git a/core/types/transaction.go b/core/types/transaction.go index 7a1d6104e4..7d34c86f47 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -129,6 +129,7 @@ func (tx *Transaction) sender() []byte { return crypto.Sha3(pubkey[1:])[12:] } +// TODO: deprecate after new accounts & key stores are integrated func (tx *Transaction) Sign(privk []byte) error { sig := tx.Signature(privk) @@ -140,6 +141,13 @@ func (tx *Transaction) Sign(privk []byte) error { return nil } +func (tx *Transaction) SetSignatureValues(sig []byte) error { + tx.R = sig[:32] + tx.S = sig[32:64] + tx.V = uint64(sig[64] + 27) + return nil +} + func (tx *Transaction) SignECDSA(key *ecdsa.PrivateKey) error { return tx.Sign(crypto.FromECDSA(key)) } diff --git a/crypto/key.go b/crypto/key.go index ec4908c30e..9dbf374675 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -26,7 +26,6 @@ package crypto import ( "bytes" "crypto/ecdsa" - "crypto/elliptic" "encoding/json" "io" @@ -87,18 +86,16 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { } func NewKey(rand io.Reader) *Key { - randBytes := make([]byte, 32) + randBytes := make([]byte, 64) _, err := rand.Read(randBytes) if err != nil { panic("key generation: could not read from random source: " + err.Error()) } reader := bytes.NewReader(randBytes) - _, x, y, err := elliptic.GenerateKey(S256(), reader) + privateKeyECDSA, err := ecdsa.GenerateKey(S256(), reader) if err != nil { - panic("key generation: elliptic.GenerateKey failed: " + err.Error()) + panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) } - privateKeyMarshalled := elliptic.Marshal(S256(), x, y) - privateKeyECDSA := ToECDSA(privateKeyMarshalled) id := uuid.NewRandom() key := &Key{ diff --git a/crypto/key_manager.go b/crypto/key_manager.go deleted file mode 100644 index 326e559e07..0000000000 --- a/crypto/key_manager.go +++ /dev/null @@ -1,134 +0,0 @@ -package crypto - -import ( - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" -) - -var keylogger = logger.NewLogger("KEY") - -type KeyManager struct { - keyRing *KeyRing - session string - keyStore KeyStore // interface - keyRings map[string]*KeyRing // cache - keyPair *KeyPair -} - -func NewDBKeyManager(db ethutil.Database) *KeyManager { - return &KeyManager{keyStore: &DBKeyStore{db: db}, keyRings: make(map[string]*KeyRing)} -} - -func NewFileKeyManager(basedir string) *KeyManager { - return &KeyManager{keyStore: &FileKeyStore{basedir: basedir}, keyRings: make(map[string]*KeyRing)} -} - -func (k *KeyManager) KeyPair() *KeyPair { - return k.keyPair -} - -func (k *KeyManager) KeyRing() *KeyPair { - return k.keyPair -} - -func (k *KeyManager) PrivateKey() []byte { - return k.keyPair.PrivateKey -} - -func (k *KeyManager) PublicKey() []byte { - return k.keyPair.PublicKey -} - -func (k *KeyManager) Address() []byte { - return k.keyPair.Address() -} - -func (k *KeyManager) save(session string, keyRing *KeyRing) error { - err := k.keyStore.Save(session, keyRing) - if err != nil { - return err - } - k.keyRings[session] = keyRing - return nil -} - -func (k *KeyManager) load(session string) (*KeyRing, error) { - keyRing, found := k.keyRings[session] - if !found { - var err error - keyRing, err = k.keyStore.Load(session) - if err != nil { - return nil, err - } - } - return keyRing, nil -} - -func cursorError(cursor int, len int) error { - return fmt.Errorf("cursor %d out of range (0..%d)", cursor, len) -} - -func (k *KeyManager) reset(session string, cursor int, keyRing *KeyRing) error { - if cursor >= keyRing.Len() { - return cursorError(cursor, keyRing.Len()) - } - lock := &sync.Mutex{} - lock.Lock() - defer lock.Unlock() - err := k.save(session, keyRing) - if err != nil { - return err - } - k.session = session - k.keyRing = keyRing - k.keyPair = keyRing.GetKeyPair(cursor) - return nil -} - -func (k *KeyManager) SetCursor(cursor int) error { - if cursor >= k.keyRing.Len() { - return cursorError(cursor, k.keyRing.Len()) - } - k.keyPair = k.keyRing.GetKeyPair(cursor) - return nil -} - -func (k *KeyManager) Init(session string, cursor int, force bool) error { - var keyRing *KeyRing - if !force { - var err error - keyRing, err = k.load(session) - if err != nil { - return err - } - } - if keyRing == nil { - keyRing = NewGeneratedKeyRing(1) - keylogger.Infof("Created keypair. Private key: %x\n", keyRing.keys[0].PrivateKey) - } - return k.reset(session, cursor, keyRing) -} - -func (k *KeyManager) InitFromSecretsFile(session string, cursor int, secretsfile string) error { - keyRing, err := NewKeyRingFromFile(secretsfile) - if err != nil { - return err - } - return k.reset(session, cursor, keyRing) -} - -func (k *KeyManager) InitFromString(session string, cursor int, secrets string) error { - keyRing, err := NewKeyRingFromString(secrets) - if err != nil { - return err - } - return k.reset(session, cursor, keyRing) -} - -func (k *KeyManager) Export(dir string) error { - fileKeyStore := FileKeyStore{dir} - return fileKeyStore.Save(k.session, k.keyRing) -} diff --git a/crypto/key_store.go b/crypto/key_store.go deleted file mode 100644 index 04560a04ed..0000000000 --- a/crypto/key_store.go +++ /dev/null @@ -1,113 +0,0 @@ -package crypto - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - "github.com/ethereum/go-ethereum/ethutil" -) - -type KeyStore interface { - Load(string) (*KeyRing, error) - Save(string, *KeyRing) error -} - -type DBKeyStore struct { - db ethutil.Database -} - -const dbKeyPrefix = "KeyRing" - -func (k *DBKeyStore) dbKey(session string) []byte { - return []byte(fmt.Sprintf("%s%s", dbKeyPrefix, session)) -} - -func (k *DBKeyStore) Save(session string, keyRing *KeyRing) error { - k.db.Put(k.dbKey(session), keyRing.RlpEncode()) - return nil -} - -func (k *DBKeyStore) Load(session string) (*KeyRing, error) { - data, err := k.db.Get(k.dbKey(session)) - if err != nil { - return nil, nil - } - var keyRing *KeyRing - keyRing, err = NewKeyRingFromBytes(data) - if err != nil { - return nil, err - } - // if empty keyRing is found we return nil, no error - if keyRing.Len() == 0 { - return nil, nil - } - return keyRing, nil -} - -type FileKeyStore struct { - basedir string -} - -func (k *FileKeyStore) Save(session string, keyRing *KeyRing) error { - var content []byte - var err error - var privateKeys []string - var publicKeys []string - var mnemonics []string - var addresses []string - keyRing.Each(func(keyPair *KeyPair) { - privateKeys = append(privateKeys, ethutil.Bytes2Hex(keyPair.PrivateKey)) - publicKeys = append(publicKeys, ethutil.Bytes2Hex(keyPair.PublicKey)) - addresses = append(addresses, ethutil.Bytes2Hex(keyPair.Address())) - mnemonics = append(mnemonics, keyPair.Mnemonic()) - }) - - basename := session - if session == "" { - basename = "default" - } - - path := path.Join(k.basedir, basename) - content = []byte(strings.Join(privateKeys, "\n")) - err = ioutil.WriteFile(path+".prv", content, 0600) - if err != nil { - return err - } - - content = []byte(strings.Join(publicKeys, "\n")) - err = ioutil.WriteFile(path+".pub", content, 0644) - if err != nil { - return err - } - - content = []byte(strings.Join(addresses, "\n")) - err = ioutil.WriteFile(path+".addr", content, 0644) - if err != nil { - return err - } - - content = []byte(strings.Join(mnemonics, "\n")) - err = ioutil.WriteFile(path+".mne", content, 0600) - if err != nil { - return err - } - - return nil -} - -func (k *FileKeyStore) Load(session string) (*KeyRing, error) { - basename := session - if session == "" { - basename = "default" - } - secfile := path.Join(k.basedir, basename+".prv") - _, err := os.Stat(secfile) - // if file is not found then we return nil, no error - if err != nil { - return nil, nil - } - return NewKeyRingFromFile(secfile) -} diff --git a/crypto/keyring.go b/crypto/keyring.go deleted file mode 100644 index eab13dbc4b..0000000000 --- a/crypto/keyring.go +++ /dev/null @@ -1,123 +0,0 @@ -package crypto - -import ( - "fmt" - "io/ioutil" - "strings" - - "github.com/ethereum/go-ethereum/ethutil" -) - -type KeyRing struct { - keys []*KeyPair -} - -func NewKeyRing() *KeyRing { - return &KeyRing{} -} - -func (k *KeyRing) AddKeyPair(keyPair *KeyPair) { - k.keys = append(k.keys, keyPair) -} - -func (k *KeyRing) GetKeyPair(i int) *KeyPair { - if len(k.keys) > i { - return k.keys[i] - } - - return nil -} - -func (k *KeyRing) Empty() bool { - return k.Len() == 0 -} - -func (k *KeyRing) Len() int { - return len(k.keys) -} - -func (k *KeyRing) Each(f func(*KeyPair)) { - for _, keyPair := range k.keys { - f(keyPair) - } -} - -func NewGeneratedKeyRing(len int) *KeyRing { - keyRing := NewKeyRing() - for i := 0; i < len; i++ { - keyRing.AddKeyPair(GenerateNewKeyPair()) - } - return keyRing -} - -func NewKeyRingFromFile(secfile string) (*KeyRing, error) { - var content []byte - var err error - content, err = ioutil.ReadFile(secfile) - if err != nil { - return nil, err - } - keyRing, err := NewKeyRingFromString(string(content)) - if err != nil { - return nil, err - } - return keyRing, nil -} - -func NewKeyRingFromString(content string) (*KeyRing, error) { - secretStrings := strings.Split(content, "\n") - var secrets [][]byte - for _, secretString := range secretStrings { - secret := secretString - words := strings.Split(secretString, " ") - if len(words) == 24 { - secret = MnemonicDecode(words) - } else if len(words) != 1 { - return nil, fmt.Errorf("Unrecognised key format") - } - - if len(secret) != 0 { - secrets = append(secrets, ethutil.Hex2Bytes(secret)) - } - } - - return NewKeyRingFromSecrets(secrets) -} - -func NewKeyRingFromSecrets(secs [][]byte) (*KeyRing, error) { - keyRing := NewKeyRing() - for _, sec := range secs { - keyPair, err := NewKeyPairFromSec(sec) - if err != nil { - return nil, err - } - keyRing.AddKeyPair(keyPair) - } - return keyRing, nil -} - -func NewKeyRingFromBytes(data []byte) (*KeyRing, error) { - var secrets [][]byte - it := ethutil.NewValueFromBytes(data).NewIterator() - for it.Next() { - secret := it.Value().Bytes() - secrets = append(secrets, secret) - } - keyRing, err := NewKeyRingFromSecrets(secrets) - if err != nil { - return nil, err - } - return keyRing, nil -} - -func (k *KeyRing) RlpEncode() []byte { - return k.RlpValue().Encode() -} - -func (k *KeyRing) RlpValue() *ethutil.Value { - v := ethutil.EmptyValue() - k.Each(func(keyPair *KeyPair) { - v.Append(keyPair.RlpValue()) - }) - return v -} diff --git a/crypto/keys_test.go b/crypto/keys_test.go deleted file mode 100644 index 56e8519696..0000000000 --- a/crypto/keys_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package crypto - -// import ( -// "github.com/ethereum/go-ethereum/ethdb" -// // "io/ioutil" -// "fmt" -// "os" -// "path" -// "testing" -// ) - -// // test if persistence layer works -// func TestDBKeyManager(t *testing.T) { -// memdb, _ := ethdb.NewMemDatabase() -// keyManager0 := NewDBKeyManager(memdb) -// err := keyManager0.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// keyManager1 := NewDBKeyManager(memdb) -// err = keyManager1.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// if string(keyManager0.PrivateKey()) != string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be identical via db persistence", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } -// err = keyManager1.Init("", 0, true) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// if string(keyManager0.PrivateKey()) == string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be be different despite db persistence if force generate", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } -// } - -// func TestFileKeyManager(t *testing.T) { -// basedir0 := "/tmp/ethtest0" -// os.RemoveAll(basedir0) -// os.Mkdir(basedir0, 0777) - -// keyManager0 := NewFileKeyManager(basedir0) -// err := keyManager0.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } - -// keyManager1 := NewFileKeyManager(basedir0) - -// err = keyManager1.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// if string(keyManager0.PrivateKey()) != string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be identical via db persistence", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } - -// err = keyManager1.Init("", 0, true) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// if string(keyManager0.PrivateKey()) == string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be be different despite db persistence if force generate", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } -// } - -// // cursor errors -// func TestCursorErrors(t *testing.T) { -// memdb, _ := ethdb.NewMemDatabase() -// keyManager0 := NewDBKeyManager(memdb) -// err := keyManager0.Init("", 0, false) -// err = keyManager0.Init("", 1, false) -// if err == nil { -// t.Error("Expected cursor error") -// } -// err = keyManager0.SetCursor(1) -// if err == nil { -// t.Error("Expected cursor error") -// } -// } - -// func TestExportImport(t *testing.T) { -// memdb, _ := ethdb.NewMemDatabase() -// keyManager0 := NewDBKeyManager(memdb) -// err := keyManager0.Init("", 0, false) -// basedir0 := "/tmp/ethtest0" -// os.RemoveAll(basedir0) -// os.Mkdir(basedir0, 0777) -// keyManager0.Export(basedir0) - -// keyManager1 := NewFileKeyManager(basedir0) -// err = keyManager1.Init("", 0, false) -// if err != nil { -// t.Error("Unexpected error: ", err) -// } -// fmt.Printf("keyRing: %v\n", keyManager0.KeyPair()) -// fmt.Printf("keyRing: %v\n", keyManager1.KeyPair()) -// if string(keyManager0.PrivateKey()) != string(keyManager1.PrivateKey()) { -// t.Error("Expected private keys %x, %x, to be identical via export to filestore basedir", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// } -// path.Join("") - -// // memdb, _ = ethdb.NewMemDatabase() -// // keyManager2 := NewDBKeyManager(memdb) -// // err = keyManager2.InitFromSecretsFile("", 0, path.Join(basedir0, "default.prv")) -// // if err != nil { -// // t.Error("Unexpected error: ", err) -// // } -// // if string(keyManager0.PrivateKey()) != string(keyManager2.PrivateKey()) { -// // t.Error("Expected private keys %s, %s, to be identical via export/import prv", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// // } - -// // memdb, _ = ethdb.NewMemDatabase() -// // keyManager3 := NewDBKeyManager(memdb) -// // err = keyManager3.InitFromSecretsFile("", 0, path.Join(basedir0, "default.mne")) -// // if err != nil { -// // t.Error("Unexpected error: ", err) -// // } -// // if string(keyManager0.PrivateKey()) != string(keyManager3.PrivateKey()) { -// // t.Error("Expected private keys %s, %s, to be identical via export/import mnemonic file", keyManager0.PrivateKey(), keyManager1.PrivateKey()) -// // } -// } diff --git a/eth/backend.go b/eth/backend.go index 584d60c7e8..9e365161dd 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/ethereum/ethash" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/blockpool" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" @@ -19,13 +20,12 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/vm" "github.com/ethereum/go-ethereum/whisper" ) var ( - ethlogger = logger.NewLogger("SERV") + servlogger = logger.NewLogger("SERV") jsonlogger = logger.NewJsonLogger() defaultBootNodes = []*discover.Node{ @@ -38,11 +38,9 @@ var ( type Config struct { Name string - KeyStore string DataDir string LogFile string LogLevel int - KeyRing string LogFormat string VmDebug bool @@ -61,9 +59,8 @@ type Config struct { Shh bool Dial bool - MinerThreads int - - KeyManager *crypto.KeyManager + MinerThreads int + AccountManager *accounts.Manager } func (cfg *Config) parseBootNodes() []*discover.Node { @@ -77,7 +74,7 @@ func (cfg *Config) parseBootNodes() []*discover.Node { } n, err := discover.ParseNode(url) if err != nil { - ethlogger.Errorf("Bootstrap URL %s: %v\n", url, err) + servlogger.Errorf("Bootstrap URL %s: %v\n", url, err) continue } ns = append(ns, n) @@ -101,7 +98,7 @@ func (cfg *Config) nodeKey() (*ecdsa.PrivateKey, error) { return nil, fmt.Errorf("could not generate server key: %v", err) } if err := ioutil.WriteFile(keyfile, crypto.FromECDSA(key), 0600); err != nil { - ethlogger.Errorln("could not persist nodekey: ", err) + servlogger.Errorln("could not persist nodekey: ", err) } return key, nil } @@ -120,6 +117,7 @@ type Ethereum struct { txPool *core.TxPool chainManager *core.ChainManager blockPool *blockpool.BlockPool + accountManager *accounts.Manager whisper *whisper.Whisper net *p2p.Server @@ -128,9 +126,6 @@ type Ethereum struct { blockSub event.Subscription miner *miner.Miner - RpcServer rpc.RpcServer - keyManager *crypto.KeyManager - logger logger.LogSystem Mining bool @@ -139,7 +134,7 @@ type Ethereum struct { func New(config *Config) (*Ethereum, error) { // Boostrap database - ethlogger := logger.New(config.DataDir, config.LogFile, config.LogLevel, config.LogFormat) + servlogger := logger.New(config.DataDir, config.LogFile, config.LogLevel, config.LogFormat) blockDb, err := ethdb.NewLDBDatabase(path.Join(config.DataDir, "blockchain")) if err != nil { @@ -158,40 +153,31 @@ func New(config *Config) (*Ethereum, error) { return nil, fmt.Errorf("Database version mismatch. Protocol(%d / %d). `rm -rf %s`", protov, ProtocolVersion, path) } - // Create new keymanager - var keyManager *crypto.KeyManager - switch config.KeyStore { - case "db": - keyManager = crypto.NewDBKeyManager(blockDb) - case "file": - keyManager = crypto.NewFileKeyManager(config.DataDir) - default: - return nil, fmt.Errorf("unknown keystore type: %s", config.KeyStore) - } - // Initialise the keyring - keyManager.Init(config.KeyRing, 0, false) - saveProtocolVersion(blockDb) //ethutil.Config.Db = db eth := &Ethereum{ - shutdownChan: make(chan bool), - blockDb: blockDb, - stateDb: stateDb, - keyManager: keyManager, - eventMux: &event.TypeMux{}, - logger: ethlogger, - DataDir: config.DataDir, + shutdownChan: make(chan bool), + blockDb: blockDb, + stateDb: stateDb, + eventMux: &event.TypeMux{}, + logger: servlogger, + accountManager: config.AccountManager, + DataDir: config.DataDir, + } + + cb, err := eth.accountManager.Coinbase() + if err != nil { + return nil, 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,10 +256,21 @@ 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 } +func (s *Ethereum) StartForTest() { + jsonlogger.LogJson(&logger.LogStarting{ + ClientString: s.net.Name, + ProtocolVersion: ProtocolVersion, + }) + + // Start services + s.txPool.Start() + s.blockPool.Start() +} + func (self *Ethereum) SuggestPeer(nodeURL string) error { n, err := discover.ParseNode(nodeURL) if err != nil { @@ -292,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() @@ -303,7 +295,7 @@ func (s *Ethereum) Stop() { s.whisper.Stop() } - ethlogger.Infoln("Server stopped") + servlogger.Infoln("Server stopped") close(s.shutdownChan) } diff --git a/ethdb/database_test.go b/ethdb/database_test.go index 2cbaf58e0d..7de30fd814 100644 --- a/ethdb/database_test.go +++ b/ethdb/database_test.go @@ -7,8 +7,6 @@ import ( ) func TestCompression(t *testing.T) { - ethutil.ReadConfig("", "/tmp", "") - db, err := NewLDBDatabase("testdb") if err != nil { t.Fatal(err) diff --git a/javascript/javascript_runtime.go b/javascript/javascript_runtime.go index 36b14a057d..0a137f72a0 100644 --- a/javascript/javascript_runtime.go +++ b/javascript/javascript_runtime.go @@ -6,6 +6,7 @@ import ( "os" "path" "path/filepath" + "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/xeth" "github.com/obscuren/otto" diff --git a/javascript/types.go b/javascript/types.go index 77e209d194..5f47c17356 100644 --- a/javascript/types.go +++ b/javascript/types.go @@ -58,7 +58,7 @@ type JSEthereum struct { func (self *JSEthereum) Block(v interface{}) otto.Value { if number, ok := v.(int64); ok { - return self.toVal(&JSBlock{self.XEth.BlockByNumber(int32(number)), self}) + return self.toVal(&JSBlock{self.XEth.BlockByNumber(number), self}) } else if hash, ok := v.(string); ok { return self.toVal(&JSBlock{self.XEth.BlockByHash(hash), self}) } @@ -70,8 +70,8 @@ func (self *JSEthereum) GetStateObject(addr string) otto.Value { return self.toVal(&JSStateObject{self.XEth.State().SafeGet(addr), self}) } -func (self *JSEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value { - r, err := self.XEth.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr) +func (self *JSEthereum) Transact(fromStr, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value { + r, err := self.XEth.Transact(fromStr, recipient, valueStr, gasStr, gasPriceStr, dataStr) if err != nil { fmt.Println(err) diff --git a/miner/worker.go b/miner/worker.go index 21a0522e8a..61091f3c08 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -209,6 +209,8 @@ gasLimit: err := self.commitTransaction(tx) switch { case core.IsNonceErr(err): + fallthrough + case core.IsInvalidTxErr(err): // Remove invalid transactions remove = append(remove, tx) case state.IsGasLimitErr(err): @@ -222,7 +224,7 @@ gasLimit: } self.eth.TxPool().RemoveSet(remove) - self.current.coinbase.AddBalance(core.BlockReward) + self.current.state.AddBalance(self.coinbase, core.BlockReward) self.current.state.Update(ethutil.Big0) self.push() @@ -258,9 +260,11 @@ func (self *worker) commitUncle(uncle *types.Header) error { } func (self *worker) commitTransaction(tx *types.Transaction) error { + snap := self.current.state.Copy() //fmt.Printf("proc %x %v\n", tx.Hash()[:3], tx.Nonce()) receipt, _, err := self.proc.ApplyTransaction(self.current.coinbase, self.current.state, self.current.block, tx, self.current.totalUsedGas, true) - if err != nil && (core.IsNonceErr(err) || state.IsGasLimitErr(err)) { + if err != nil && (core.IsNonceErr(err) || state.IsGasLimitErr(err) || core.IsInvalidTxErr(err)) { + self.current.state.Set(snap) return err } diff --git a/rpc/api.go b/rpc/api.go index 70a8cf9b4b..8bbe80bd83 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -1,15 +1,10 @@ -/* -For each request type, define the following: - -1. RpcRequest "To" method [message.go], which does basic validation and conversion to "Args" type via json.Decoder() -2. json.Decoder() calls "UnmarshalON" defined on each "Args" struct -3. EthereumApi method, taking the "Args" type and replying with an interface to be marshalled to ON - -*/ package rpc import ( + "encoding/json" + "fmt" "math/big" + "path" "strings" "sync" "time" @@ -26,8 +21,8 @@ import ( ) var ( - defaultGasPrice = big.NewInt(10000000000000) - defaultGas = big.NewInt(10000) + defaultGasPrice = big.NewInt(150000000000) + defaultGas = big.NewInt(500000) filterTickerTime = 15 * time.Second ) @@ -49,21 +44,18 @@ type EthereumApi struct { register map[string][]*NewTxArgs db ethutil.Database - - 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(), - quit: make(chan struct{}), - filterManager: filter.NewFilterManager(eth.Backend().EventMux()), - logs: make(map[int]*logFilter), - messages: make(map[int]*whisperFilter), - db: db, - defaultBlockAge: -1, + eth: eth, + mux: eth.Backend().EventMux(), + quit: make(chan struct{}), + filterManager: filter.NewFilterManager(eth.Backend().EventMux()), + logs: make(map[int]*logFilter), + messages: make(map[int]*whisperFilter), + db: db, } go api.filterManager.Start() go api.start() @@ -71,36 +63,33 @@ func NewEthereumApi(eth *xeth.XEth) *EthereumApi { return api } -func (self *EthereumApi) setStateByBlockNumber(num int64) { +func (self *EthereumApi) xethWithStateNum(num int64) *xeth.XEth { chain := self.xeth().Backend().ChainManager() var block *types.Block - if self.defaultBlockAge < 0 { + if num < 0 { num = chain.CurrentBlock().Number().Int64() + num + 1 } block = chain.GetBlockByNumber(uint64(num)) + var st *state.StateDB if block != nil { - self.useState(state.New(block.Root(), self.xeth().Backend().StateDb())) + st = state.New(block.Root(), self.xeth().Backend().StateDb()) } else { - self.useState(chain.State()) + st = chain.State() } + return self.xeth().WithState(st) +} + +func (self *EthereumApi) getStateWithNum(num int64) *xeth.State { + return self.xethWithStateNum(num).State() } func (self *EthereumApi) start() { timer := time.NewTicker(filterTickerTime) - events := self.mux.Subscribe(core.ChainEvent{}) - done: for { select { - case ev := <-events.Chan(): - switch ev.(type) { - case core.ChainEvent: - if self.defaultBlockAge < 0 { - self.setStateByBlockNumber(self.defaultBlockAge) - } - } case <-timer.C: self.logMut.Lock() self.messagesMut.Lock() @@ -129,35 +118,35 @@ func (self *EthereumApi) stop() { close(self.quit) } -func (self *EthereumApi) Register(args string, reply *interface{}) error { - self.regmut.Lock() - defer self.regmut.Unlock() +// func (self *EthereumApi) Register(args string, reply *interface{}) error { +// self.regmut.Lock() +// defer self.regmut.Unlock() - if _, ok := self.register[args]; ok { - self.register[args] = nil // register with empty - } - return nil -} +// if _, ok := self.register[args]; ok { +// self.register[args] = nil // register with empty +// } +// return nil +// } -func (self *EthereumApi) Unregister(args string, reply *interface{}) error { - self.regmut.Lock() - defer self.regmut.Unlock() +// func (self *EthereumApi) Unregister(args string, reply *interface{}) error { +// self.regmut.Lock() +// defer self.regmut.Unlock() - delete(self.register, args) +// delete(self.register, args) - return nil -} +// return nil +// } -func (self *EthereumApi) WatchTx(args string, reply *interface{}) error { - self.regmut.Lock() - defer self.regmut.Unlock() +// func (self *EthereumApi) WatchTx(args string, reply *interface{}) error { +// self.regmut.Lock() +// defer self.regmut.Unlock() - txs := self.register[args] - self.register[args] = nil +// txs := self.register[args] +// self.register[args] = nil - *reply = txs - return nil -} +// *reply = txs +// return nil +// } func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) error { var id int @@ -172,7 +161,7 @@ func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) erro id = self.filterManager.InstallFilter(filter) self.logs[id] = &logFilter{timeout: time.Now()} - *reply = id + *reply = i2hex(id) return nil } @@ -202,7 +191,7 @@ func (self *EthereumApi) NewFilterString(args string, reply *interface{}) error id = self.filterManager.InstallFilter(filter) self.logs[id] = &logFilter{timeout: time.Now()} - *reply = id + *reply = i2hex(id) return nil } @@ -239,25 +228,7 @@ func (self *EthereumApi) AllLogs(args *FilterOptions, reply *interface{}) error return nil } -func (p *EthereumApi) GetBlock(args *GetBlockArgs, reply *interface{}) error { - // This seems a bit precarious Maybe worth splitting to discrete functions - if len(args.Hash) > 0 { - *reply = p.xeth().BlockByHash(args.Hash) - } else { - *reply = p.xeth().BlockByNumber(args.BlockNumber) - } - return nil -} - -func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error { - if len(args.Gas) == 0 { - args.Gas = defaultGas.String() - } - - if len(args.GasPrice) == 0 { - args.GasPrice = defaultGasPrice.String() - } - +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) @@ -279,15 +250,27 @@ func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error { 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 - //} + // TODO: align default values to have the same type, e.g. not depend on + // ethutil.Value conversions later on + 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 + } return nil } func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error { - result, err := p.xeth().Call( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data) + result, err := p.xethWithStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data) if err != nil { return err } @@ -296,23 +279,28 @@ func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error { return nil } -func (p *EthereumApi) PushTx(args *PushTxArgs, reply *interface{}) error { - err := args.requirementsPushTx() - if err != nil { +func (p *EthereumApi) GetBalance(args *GetBalanceArgs, reply *interface{}) error { + if err := args.requirements(); err != nil { return err } - result, _ := p.xeth().PushTx(args.Tx) - *reply = result + state := p.getStateWithNum(args.BlockNumber).SafeGet(args.Address) + *reply = toHex(state.Balance().Bytes()) return nil } -func (p *EthereumApi) GetStateAt(args *GetStateArgs, reply *interface{}) error { - err := args.requirements() - if err != nil { +func (p *EthereumApi) GetStorage(args *GetStorageArgs, reply *interface{}) error { + if err := args.requirements(); err != nil { return err } + *reply = p.getStateWithNum(args.BlockNumber).SafeGet(args.Address).Storage() + return nil +} - state := p.xeth().State().SafeGet(args.Address) +func (p *EthereumApi) GetStorageAt(args *GetStorageAtArgs, reply *interface{}) error { + if err := args.requirements(); err != nil { + return err + } + state := p.getStateWithNum(args.BlockNumber).SafeGet(args.Address) value := state.StorageString(args.Key) var hx string @@ -328,115 +316,31 @@ func (p *EthereumApi) GetStateAt(args *GetStateArgs, reply *interface{}) error { return nil } -func (p *EthereumApi) GetStorageAt(args *GetStorageArgs, reply *interface{}) error { - err := args.requirements() - if err != nil { - return err - } - - *reply = p.xeth().State().SafeGet(args.Address).Storage() - return nil -} - -func (p *EthereumApi) GetPeerCount(reply *interface{}) error { - *reply = p.xeth().PeerCount() - return nil -} - -func (p *EthereumApi) GetIsListening(reply *interface{}) error { - *reply = p.xeth().IsListening() - return nil -} - -func (p *EthereumApi) GetCoinbase(reply *interface{}) error { - *reply = p.xeth().Coinbase() - return nil -} - -func (p *EthereumApi) Accounts(reply *interface{}) error { - *reply = p.xeth().Accounts() - return nil -} - -func (p *EthereumApi) GetIsMining(reply *interface{}) error { - *reply = p.xeth().IsMining() - return nil -} - -func (p *EthereumApi) SetMining(shouldmine bool, reply *interface{}) error { - *reply = p.xeth().SetMining(shouldmine) - return nil -} - -func (p *EthereumApi) GetDefaultBlockAge(reply *interface{}) error { - *reply = p.defaultBlockAge - return nil -} - -func (p *EthereumApi) SetDefaultBlockAge(defaultBlockAge int64, reply *interface{}) error { - p.defaultBlockAge = defaultBlockAge - p.setStateByBlockNumber(p.defaultBlockAge) - - *reply = true - return nil -} - -func (p *EthereumApi) BlockNumber(reply *interface{}) error { - *reply = p.xeth().Backend().ChainManager().CurrentBlock().Number() - return nil -} - func (p *EthereumApi) GetTxCountAt(args *GetTxCountArgs, reply *interface{}) error { err := args.requirements() if err != nil { return err } - *reply = p.xeth().TxCountAt(args.Address) + *reply = p.xethWithStateNum(args.BlockNumber).TxCountAt(args.Address) return nil } -func (p *EthereumApi) GetBalanceAt(args *GetBalanceArgs, reply *interface{}) error { - err := args.requirements() - if err != nil { +func (p *EthereumApi) GetData(args *GetDataArgs, reply *interface{}) error { + if err := args.requirements(); err != nil { return err } - state := p.xeth().State().SafeGet(args.Address) - *reply = toHex(state.Balance().Bytes()) - return nil -} - -func (p *EthereumApi) GetCodeAt(args *GetCodeAtArgs, reply *interface{}) error { - err := args.requirements() - if err != nil { - return err - } - *reply = p.xeth().CodeAt(args.Address) + *reply = p.xethWithStateNum(args.BlockNumber).CodeAt(args.Address) return nil } func (p *EthereumApi) GetCompilers(reply *interface{}) error { - c := []string{"serpent"} + c := []string{""} *reply = c return nil } -func (p *EthereumApi) CompileSerpent(script string, reply *interface{}) error { - res, err := ethutil.Compile(script, false) - if err != nil { - return err - } - *reply = res - return nil -} - -func (p *EthereumApi) Sha3(args *Sha3Args, reply *interface{}) error { - *reply = toHex(crypto.Sha3(fromHex(args.Data))) - return nil -} - func (p *EthereumApi) DbPut(args *DbArgs, reply *interface{}) error { - err := args.requirements() - if err != nil { + if err := args.requirements(); err != nil { return err } @@ -446,8 +350,7 @@ func (p *EthereumApi) DbPut(args *DbArgs, reply *interface{}) error { } func (p *EthereumApi) DbGet(args *DbArgs, reply *interface{}) error { - err := args.requirements() - if err != nil { + if err := args.requirements(); err != nil { return err } @@ -461,14 +364,18 @@ func (p *EthereumApi) NewWhisperIdentity(reply *interface{}) error { return nil } -func (p *EthereumApi) NewWhisperFilter(args *xeth.Options, reply *interface{}) error { +func (p *EthereumApi) NewWhisperFilter(args *WhisperFilterArgs, reply *interface{}) error { var id int - args.Fn = func(msg xeth.WhisperMessage) { + opts := new(xeth.Options) + opts.From = args.From + opts.To = args.To + opts.Topics = args.Topics + opts.Fn = func(msg xeth.WhisperMessage) { p.messagesMut.Lock() defer p.messagesMut.Unlock() p.messages[id].add(msg) // = append(p.messages[id], msg) } - id = p.xeth().Whisper().Watch(args) + id = p.xeth().Whisper().Watch(opts) p.messages[id] = &whisperFilter{timeout: time.Now()} *reply = id return nil @@ -486,7 +393,7 @@ func (self *EthereumApi) MessagesChanged(id int, reply *interface{}) error { } func (p *EthereumApi) WhisperPost(args *WhisperMessageArgs, reply *interface{}) error { - err := p.xeth().Whisper().Post(args.Payload, args.To, args.From, args.Topic, args.Priority, args.Ttl) + err := p.xeth().Whisper().Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl) if err != nil { return err } @@ -505,199 +412,358 @@ func (p *EthereumApi) WhisperMessages(id int, reply *interface{}) error { return nil } +func (p *EthereumApi) GetBlockByHash(blockhash string, includetx bool) (*BlockRes, error) { + block := p.xeth().EthBlockByHash(blockhash) + br := NewBlockRes(block) + br.fullTx = includetx + return br, nil +} + +func (p *EthereumApi) GetBlockByNumber(blocknum int64, includetx bool) (*BlockRes, error) { + block := p.xeth().EthBlockByNumber(blocknum) + br := NewBlockRes(block) + br.fullTx = includetx + return br, nil +} + +func (p *EthereumApi) GetBlockTransactionCountByHash(blockhash string) (int64, error) { + block := p.xeth().EthBlockByHash(blockhash) + br := NewBlockRes(block) + return int64(len(br.Transactions)), nil +} + +func (p *EthereumApi) GetBlockTransactionCountByNumber(blocknum int64) (int64, error) { + block := p.xeth().EthBlockByNumber(blocknum) + br := NewBlockRes(block) + return int64(len(br.Transactions)), nil +} + +func (p *EthereumApi) GetBlockUncleCountByHash(blockhash string) (int64, error) { + block := p.xeth().EthBlockByHash(blockhash) + br := NewBlockRes(block) + return int64(len(br.Uncles)), nil +} + +func (p *EthereumApi) GetBlockUncleCountByNumber(blocknum int64) (int64, error) { + block := p.xeth().EthBlockByNumber(blocknum) + br := NewBlockRes(block) + return int64(len(br.Uncles)), nil +} + func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error { // Spec at https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC - rpclogger.DebugDetailf("%T %s", req.Params, req.Params) + rpclogger.Debugf("%s %s", req.Method, req.Params) switch req.Method { + case "web3_sha3": + args := new(Sha3Args) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + *reply = toHex(crypto.Sha3(fromHex(args.Data))) + case "net_listening": + *reply = p.xeth().IsListening() + case "net_peerCount": + *reply = toHex(big.NewInt(int64(p.xeth().PeerCount())).Bytes()) case "eth_coinbase": - return p.GetCoinbase(reply) - case "eth_listening": - return p.GetIsListening(reply) + *reply = p.xeth().Coinbase() case "eth_mining": - return p.GetIsMining(reply) - case "eth_setMining": - args, err := req.ToBoolArgs() - if err != nil { - return err - } - return p.SetMining(args, reply) - case "eth_defaultBlock": - return p.GetDefaultBlockAge(reply) - case "eth_setDefaultBlock": - args, err := req.ToIntArgs() - if err != nil { - return err - } - return p.SetDefaultBlockAge(int64(args), reply) - case "eth_peerCount": - return p.GetPeerCount(reply) - case "eth_number": - return p.BlockNumber(reply) + *reply = p.xeth().IsMining() + case "eth_gasPrice": + *reply = toHex(defaultGasPrice.Bytes()) case "eth_accounts": - return p.Accounts(reply) - case "eth_countAt": - args, err := req.ToGetTxCountArgs() - if err != nil { + *reply = p.xeth().Accounts() + case "eth_blockNumber": + *reply = toHex(p.xeth().Backend().ChainManager().CurrentBlock().Number().Bytes()) + case "eth_getBalance": + args := new(GetBalanceArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.GetTxCountAt(args, reply) - case "eth_codeAt": - args, err := req.ToGetCodeAtArgs() - if err != nil { + return p.GetBalance(args, reply) + case "eth_getStorage", "eth_storageAt": + args := new(GetStorageArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.GetCodeAt(args, reply) - case "eth_balanceAt": - args, err := req.ToGetBalanceArgs() - if err != nil { - return err - } - return p.GetBalanceAt(args, reply) - case "eth_stateAt": - args, err := req.ToGetStateArgs() - if err != nil { - return err - } - return p.GetStateAt(args, reply) - case "eth_storageAt": - args, err := req.ToStorageAtArgs() - if err != nil { + return p.GetStorage(args, reply) + case "eth_getStorageAt": + args := new(GetStorageAtArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } return p.GetStorageAt(args, reply) - case "eth_blockByNumber", "eth_blockByHash": - args, err := req.ToGetBlockArgs() + case "eth_getTransactionCount": + args := new(GetTxCountArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + return p.GetTxCountAt(args, reply) + case "eth_getBlockTransactionCountByHash": + args := new(GetBlockByHashArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockTransactionCountByHash(args.BlockHash) if err != nil { return err } - return p.GetBlock(args, reply) - case "eth_transact": - args, err := req.ToNewTxArgs() + *reply = toHex(big.NewInt(v).Bytes()) + case "eth_getBlockTransactionCountByNumber": + args := new(GetBlockByNumberArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockTransactionCountByNumber(args.BlockNumber) if err != nil { return err } + *reply = toHex(big.NewInt(v).Bytes()) + case "eth_getUncleCountByBlockHash": + args := new(GetBlockByHashArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockUncleCountByHash(args.BlockHash) + if err != nil { + return err + } + *reply = toHex(big.NewInt(v).Bytes()) + case "eth_getUncleCountByBlockNumber": + args := new(GetBlockByNumberArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockUncleCountByNumber(args.BlockNumber) + if err != nil { + return err + } + *reply = toHex(big.NewInt(v).Bytes()) + case "eth_getData": + args := new(GetDataArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + return p.GetData(args, reply) + case "eth_sendTransaction", "eth_transact": + args := new(NewTxArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } return p.Transact(args, reply) case "eth_call": - args, err := req.ToNewTxArgs() - if err != nil { + args := new(NewTxArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } return p.Call(args, reply) - case "eth_newFilter": - args, err := req.ToFilterArgs() + case "eth_flush": + return errNotImplemented + case "eth_getBlockByHash": + args := new(GetBlockByHashArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockByHash(args.BlockHash, args.Transactions) if err != nil { return err } + *reply = v + case "eth_getBlockByNumber": + args := new(GetBlockByNumberArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockByNumber(args.BlockNumber, args.Transactions) + if err != nil { + return err + } + *reply = v + case "eth_getTransactionByHash": + return errNotImplemented + case "eth_getTransactionByBlockHashAndIndex": + args := new(HashIndexArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockByHash(args.BlockHash, true) + if err != nil { + return err + } + if args.Index > int64(len(v.Transactions)) || args.Index < 0 { + return NewErrorWithMessage(errDecodeArgs, "Transaction index does not exist") + } + *reply = v.Transactions[args.Index] + case "eth_getTransactionByBlockNumberAndIndex": + args := new(BlockNumIndexArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockByNumber(args.BlockNumber, true) + if err != nil { + return err + } + if args.Index > int64(len(v.Transactions)) || args.Index < 0 { + return NewErrorWithMessage(errDecodeArgs, "Transaction index does not exist") + } + *reply = v.Transactions[args.Index] + case "eth_getUncleByBlockHashAndIndex": + args := new(HashIndexArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockByHash(args.BlockHash, false) + if err != nil { + return err + } + if args.Index > int64(len(v.Uncles)) || args.Index < 0 { + return NewErrorWithMessage(errDecodeArgs, "Uncle index does not exist") + } + + uncle, err := p.GetBlockByHash(toHex(v.Uncles[args.Index]), false) + if err != nil { + return err + } + *reply = uncle + case "eth_getUncleByBlockNumberAndIndex": + args := new(BlockNumIndexArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + + v, err := p.GetBlockByNumber(args.BlockNumber, true) + if err != nil { + return err + } + if args.Index > int64(len(v.Uncles)) || args.Index < 0 { + return NewErrorWithMessage(errDecodeArgs, "Uncle index does not exist") + } + + uncle, err := p.GetBlockByHash(toHex(v.Uncles[args.Index]), false) + if err != nil { + return err + } + *reply = uncle + case "eth_getCompilers": + return p.GetCompilers(reply) + case "eth_compileSolidity": + case "eth_compileLLL": + case "eth_compileSerpent": + return errNotImplemented + case "eth_newFilter": + args := new(FilterOptions) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } return p.NewFilter(args, reply) - case "eth_newFilterString": - args, err := req.ToFilterStringArgs() - if err != nil { + case "eth_newBlockFilter": + args := new(FilterStringArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.NewFilterString(args, reply) + return p.NewFilterString(args.Word, reply) case "eth_uninstallFilter": - args, err := req.ToUninstallFilterArgs() - if err != nil { + args := new(FilterIdArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.UninstallFilter(args, reply) - case "eth_changed": - args, err := req.ToIdArgs() - if err != nil { + return p.UninstallFilter(args.Id, reply) + case "eth_getFilterChanges": + args := new(FilterIdArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.FilterChanged(args, reply) - case "eth_filterLogs": - args, err := req.ToIdArgs() - if err != nil { + return p.FilterChanged(args.Id, reply) + case "eth_getFilterLogs": + args := new(FilterIdArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.Logs(args, reply) - case "eth_logs": - args, err := req.ToFilterArgs() - if err != nil { + return p.Logs(args.Id, reply) + case "eth_getLogs": + args := new(FilterOptions) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } return p.AllLogs(args, reply) - case "eth_gasPrice": - *reply = toHex(defaultGasPrice.Bytes()) - return nil - case "eth_register": - args, err := req.ToRegisterArgs() - if err != nil { - return err - } - return p.Register(args, reply) - case "eth_unregister": - args, err := req.ToRegisterArgs() - if err != nil { - return err - } - return p.Unregister(args, reply) - case "eth_watchTx": - args, err := req.ToWatchTxArgs() - if err != nil { - return err - } - return p.WatchTx(args, reply) - case "eth_compilers": - return p.GetCompilers(reply) - case "eth_serpent": - args, err := req.ToCompileArgs() - if err != nil { - return err - } - return p.CompileSerpent(args, reply) - case "web3_sha3": - args, err := req.ToSha3Args() - if err != nil { - return err - } - return p.Sha3(args, reply) + case "eth_getWork": + case "eth_submitWork": + return errNotImplemented case "db_put": - args, err := req.ToDbPutArgs() - if err != nil { + args := new(DbArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } return p.DbPut(args, reply) case "db_get": - args, err := req.ToDbGetArgs() - if err != nil { + args := new(DbArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } return p.DbGet(args, reply) - case "shh_newIdentity": - return p.NewWhisperIdentity(reply) - case "shh_newFilter": - args, err := req.ToWhisperFilterArgs() - if err != nil { - return err - } - return p.NewWhisperFilter(args, reply) - case "shh_changed": - args, err := req.ToIdArgs() - if err != nil { - return err - } - return p.MessagesChanged(args, reply) case "shh_post": - args, err := req.ToWhisperPostArgs() - if err != nil { + args := new(WhisperMessageArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } return p.WhisperPost(args, reply) - case "shh_haveIdentity": - args, err := req.ToWhisperHasIdentityArgs() - if err != nil { + case "shh_newIdentity": + return p.NewWhisperIdentity(reply) + case "shh_hasIdentity": + args := new(WhisperIdentityArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.HasWhisperIdentity(args, reply) + return p.HasWhisperIdentity(args.Identity, reply) + case "shh_newGroup": + case "shh_addToGroup": + return errNotImplemented + case "shh_newFilter": + args := new(WhisperFilterArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + return p.NewWhisperFilter(args, reply) + case "shh_uninstallFilter": + return errNotImplemented + case "shh_getFilterChanges": + args := new(FilterIdArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { + return err + } + return p.MessagesChanged(args.Id, reply) case "shh_getMessages": - args, err := req.ToIdArgs() - if err != nil { + args := new(FilterIdArgs) + if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.WhisperMessages(args, reply) + return p.WhisperMessages(args.Id, reply) + // case "eth_register": + // args, err := req.ToRegisterArgs() + // if err != nil { + // return err + // } + // return p.Register(args, reply) + // case "eth_unregister": + // args, err := req.ToRegisterArgs() + // if err != nil { + // return err + // } + // return p.Unregister(args, reply) + // case "eth_watchTx": + // args, err := req.ToWatchTxArgs() + // if err != nil { + // return err + // } + // return p.WatchTx(args, reply) default: return NewErrorWithMessage(errNotImplemented, req.Method) } @@ -713,9 +779,38 @@ func (self *EthereumApi) xeth() *xeth.XEth { return self.eth } -func (self *EthereumApi) useState(statedb *state.StateDB) { - self.xethMu.Lock() - defer self.xethMu.Unlock() +func toFilterOptions(options *FilterOptions) core.FilterOptions { + var opts core.FilterOptions - self.eth = self.eth.UseState(statedb) + // Convert optional address slice/string to byte slice + if str, ok := options.Address.(string); ok { + opts.Address = [][]byte{fromHex(str)} + } else if slice, ok := options.Address.([]interface{}); ok { + bslice := make([][]byte, len(slice)) + for i, addr := range slice { + if saddr, ok := addr.(string); ok { + bslice[i] = fromHex(saddr) + } + } + opts.Address = bslice + } + + opts.Earliest = options.Earliest + opts.Latest = options.Latest + + topics := make([][][]byte, len(options.Topics)) + for i, topicDat := range options.Topics { + if slice, ok := topicDat.([]interface{}); ok { + topics[i] = make([][]byte, len(slice)) + for j, topic := range slice { + topics[i][j] = fromHex(topic.(string)) + } + } else if str, ok := topicDat.(string); ok { + topics[i] = make([][]byte, 1) + topics[i][0] = fromHex(str) + } + } + opts.Topics = topics + + return opts } diff --git a/rpc/api_test.go b/rpc/api_test.go index a9fc16cd37..ec03822c5b 100644 --- a/rpc/api_test.go +++ b/rpc/api_test.go @@ -1,11 +1,29 @@ package rpc import ( + "encoding/json" "sync" "testing" "time" ) +func TestWeb3Sha3(t *testing.T) { + jsonstr := `{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}` + expected := "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" + + api := &EthereumApi{} + + var req RpcRequest + json.Unmarshal([]byte(jsonstr), &req) + + var response interface{} + _ = api.GetRequestReply(&req, &response) + + if response.(string) != expected { + t.Errorf("Expected %s got %s", expected, response) + } +} + func TestFilterClose(t *testing.T) { t.Skip() api := &EthereumApi{ diff --git a/rpc/args.go b/rpc/args.go index ea8489585e..d4d8070608 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -1,246 +1,331 @@ package rpc -import "encoding/json" +import ( + "bytes" + "encoding/json" + "math/big" -import "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/ethutil" +) -type GetBlockArgs struct { - BlockNumber int32 - Hash string +func blockNumber(raw json.RawMessage, number *int64) (err error) { + var str string + if err = json.Unmarshal(raw, &str); err != nil { + return errDecodeArgs + } + + switch str { + case "latest": + *number = -1 + case "pending": + *number = 0 + default: + *number = ethutil.String2Big(str).Int64() + } + return nil } -func (obj *GetBlockArgs) UnmarshalJSON(b []byte) (err error) { - argint, argstr := int32(0), "" - if err = json.Unmarshal(b, &argint); err == nil { - obj.BlockNumber = argint - return +type GetBlockByHashArgs struct { + BlockHash string + Transactions bool +} + +func (args *GetBlockByHashArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + r := bytes.NewReader(b) + if err := json.NewDecoder(r).Decode(&obj); err != nil { + return errDecodeArgs } - if err = json.Unmarshal(b, &argstr); err == nil { - obj.Hash = argstr - return + + if len(obj) < 1 { + return errArguments } - return errDecodeArgs + + argstr, ok := obj[0].(string) + if !ok { + return errDecodeArgs + } + args.BlockHash = argstr + + if len(obj) > 1 { + args.Transactions = obj[1].(bool) + } + + return nil +} + +type GetBlockByNumberArgs struct { + BlockNumber int64 + Transactions bool +} + +func (args *GetBlockByNumberArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + r := bytes.NewReader(b) + if err := json.NewDecoder(r).Decode(&obj); err != nil { + return errDecodeArgs + } + + if len(obj) < 1 { + return errArguments + } + + if v, ok := obj[0].(float64); ok { + args.BlockNumber = int64(v) + } else { + args.BlockNumber = ethutil.Big(obj[0].(string)).Int64() + } + + if len(obj) > 1 { + args.Transactions = obj[1].(bool) + } + + return nil } type NewTxArgs struct { - From string `json:"from"` - To string `json:"to"` - Value string `json:"value"` - Gas string `json:"gas"` - GasPrice string `json:"gasPrice"` - Data string `json:"data"` + From string + To string + Value *big.Int + Gas *big.Int + GasPrice *big.Int + Data string + + BlockNumber int64 } -func (obj *NewTxArgs) UnmarshalJSON(b []byte) (err error) { - // Data can be either specified as "data" or "code" :-/ - var ext struct { - From string - To string - Value string - Gas string - GasPrice string - Data string - Code string +func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) { + var obj struct{ From, To, Value, Gas, GasPrice, Data string } + if err = UnmarshalRawMessages(b, &obj, &args.BlockNumber); err != nil { + return err } - if err = json.Unmarshal(b, &ext); err == nil { - if len(ext.Data) == 0 { - ext.Data = ext.Code - } - obj.From = ext.From - obj.To = ext.To - obj.Value = ext.Value - obj.Gas = ext.Gas - obj.GasPrice = ext.GasPrice - obj.Data = ext.Data + args.From = obj.From + args.To = obj.To + args.Value = ethutil.Big(obj.Value) + args.Gas = ethutil.Big(obj.Gas) + args.GasPrice = ethutil.Big(obj.GasPrice) + args.Data = obj.Data - return - } - - return errDecodeArgs -} - -type PushTxArgs struct { - Tx string `json:"tx"` -} - -func (obj *PushTxArgs) UnmarshalJSON(b []byte) (err error) { - arg0 := "" - if err = json.Unmarshal(b, &arg0); err == nil { - obj.Tx = arg0 - return - } - return errDecodeArgs -} - -func (a *PushTxArgs) requirementsPushTx() error { - if a.Tx == "" { - return NewErrorWithMessage(errArguments, "PushTx requires a 'tx' as argument") - } return nil } type GetStorageArgs struct { - Address string + Address string + BlockNumber int64 } -func (obj *GetStorageArgs) UnmarshalJSON(b []byte) (err error) { - if err = json.Unmarshal(b, &obj.Address); err != nil { +func (args *GetStorageArgs) UnmarshalJSON(b []byte) (err error) { + if err = UnmarshalRawMessages(b, &args.Address, &args.BlockNumber); err != nil { return errDecodeArgs } - return + + return nil } -func (a *GetStorageArgs) requirements() error { - if len(a.Address) == 0 { - return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'address' value as argument") +func (args *GetStorageArgs) requirements() error { + if len(args.Address) == 0 { + return NewErrorWithMessage(errArguments, "Address cannot be blank") } return nil } -type GetStateArgs struct { - Address string - Key string +type GetStorageAtArgs struct { + Address string + Key string + BlockNumber int64 } -func (obj *GetStateArgs) UnmarshalJSON(b []byte) (err error) { - arg0 := "" - if err = json.Unmarshal(b, &arg0); err == nil { - obj.Address = arg0 - return +func (args *GetStorageAtArgs) UnmarshalJSON(b []byte) (err error) { + var obj []string + if err = UnmarshalRawMessages(b, &obj, &args.BlockNumber); err != nil { + return errDecodeArgs } - return errDecodeArgs + if len(obj) < 2 { + return errDecodeArgs + } + + args.Address = obj[0] + args.Key = obj[1] + + return nil } -func (a *GetStateArgs) requirements() error { - if a.Address == "" { - return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'address' value as argument") +func (args *GetStorageAtArgs) requirements() error { + if len(args.Address) == 0 { + return NewErrorWithMessage(errArguments, "Address cannot be blank") } - if a.Key == "" { - return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'key' value as argument") + + if len(args.Key) == 0 { + return NewErrorWithMessage(errArguments, "Key cannot be blank") } return nil } type GetTxCountArgs struct { - Address string `json:"address"` + Address string + BlockNumber int64 } -func (obj *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) { - arg0 := "" - if err = json.Unmarshal(b, &arg0); err == nil { - obj.Address = arg0 - return +func (args *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) { + if err = UnmarshalRawMessages(b, &args.Address, &args.BlockNumber); err != nil { + return errDecodeArgs } - return errDecodeArgs + + return nil } -func (a *GetTxCountArgs) requirements() error { - if a.Address == "" { - return NewErrorWithMessage(errArguments, "GetTxCountAt requires an 'address' value as argument") +func (args *GetTxCountArgs) requirements() error { + if len(args.Address) == 0 { + return NewErrorWithMessage(errArguments, "Address cannot be blank") } return nil } type GetBalanceArgs struct { - Address string + Address string + BlockNumber int64 } -func (obj *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) { - arg0 := "" - if err = json.Unmarshal(b, &arg0); err == nil { - obj.Address = arg0 - return +func (args *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) { + if err = UnmarshalRawMessages(b, &args.Address, &args.BlockNumber); err != nil { + return errDecodeArgs } - return errDecodeArgs + + return nil } -func (a *GetBalanceArgs) requirements() error { - if a.Address == "" { - return NewErrorWithMessage(errArguments, "GetBalanceAt requires an 'address' value as argument") +func (args *GetBalanceArgs) requirements() error { + if len(args.Address) == 0 { + return NewErrorWithMessage(errArguments, "Address cannot be blank") } return nil } -type GetCodeAtArgs struct { - Address string +type GetDataArgs struct { + Address string + BlockNumber int64 } -func (obj *GetCodeAtArgs) UnmarshalJSON(b []byte) (err error) { - arg0 := "" - if err = json.Unmarshal(b, &arg0); err == nil { - obj.Address = arg0 - return +func (args *GetDataArgs) UnmarshalJSON(b []byte) (err error) { + if err = UnmarshalRawMessages(b, &args.Address, &args.BlockNumber); err != nil { + return errDecodeArgs } - return errDecodeArgs + + return nil } -func (a *GetCodeAtArgs) requirements() error { - if a.Address == "" { - return NewErrorWithMessage(errArguments, "GetCodeAt requires an 'address' value as argument") +func (args *GetDataArgs) requirements() error { + if len(args.Address) == 0 { + return NewErrorWithMessage(errArguments, "Address cannot be blank") } return nil } +type BlockNumIndexArgs struct { + BlockNumber int64 + Index int64 +} + +type HashIndexArgs struct { + BlockHash string + Index int64 +} + type Sha3Args struct { Data string } -func (obj *Sha3Args) UnmarshalJSON(b []byte) (err error) { - if err = json.Unmarshal(b, &obj.Data); err != nil { - return errDecodeArgs +func (args *Sha3Args) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + r := bytes.NewReader(b) + if err := json.NewDecoder(r).Decode(&obj); err != nil { + return NewErrorWithMessage(errDecodeArgs, err.Error()) } - return + + if len(obj) < 1 { + return errArguments + } + args.Data = obj[0].(string) + + return nil } +// type FilterArgs struct { +// FromBlock uint64 +// ToBlock uint64 +// Limit uint64 +// Offset uint64 +// Address string +// Topics []string +// } + +// func (args *FilterArgs) UnmarshalJSON(b []byte) (err error) { +// var obj []struct { +// FromBlock string `json:"fromBlock"` +// ToBlock string `json:"toBlock"` +// Limit string `json:"limit"` +// Offset string `json:"offset"` +// Address string `json:"address"` +// Topics []string `json:"topics"` +// } + +// if err = json.Unmarshal(b, &obj); err != nil { +// return errDecodeArgs +// } + +// if len(obj) < 1 { +// return errArguments +// } +// args.FromBlock = uint64(ethutil.Big(obj[0].FromBlock).Int64()) +// args.ToBlock = uint64(ethutil.Big(obj[0].ToBlock).Int64()) +// args.Limit = uint64(ethutil.Big(obj[0].Limit).Int64()) +// args.Offset = uint64(ethutil.Big(obj[0].Offset).Int64()) +// args.Address = obj[0].Address +// args.Topics = obj[0].Topics + +// return nil +// } + type FilterOptions struct { Earliest int64 Latest int64 Address interface{} - Topic []interface{} + Topics []interface{} Skip int Max int } -func toFilterOptions(options *FilterOptions) core.FilterOptions { - var opts core.FilterOptions - - // Convert optional address slice/string to byte slice - if str, ok := options.Address.(string); ok { - opts.Address = [][]byte{fromHex(str)} - } else if slice, ok := options.Address.([]interface{}); ok { - bslice := make([][]byte, len(slice)) - for i, addr := range slice { - if saddr, ok := addr.(string); ok { - bslice[i] = fromHex(saddr) - } - } - opts.Address = bslice +func (args *FilterOptions) UnmarshalJSON(b []byte) (err error) { + var obj []struct { + FromBlock string `json:"fromBlock"` + ToBlock string `json:"toBlock"` + Limit string `json:"limit"` + Offset string `json:"offset"` + Address string `json:"address"` + Topics []interface{} `json:"topics"` } - opts.Earliest = options.Earliest - opts.Latest = options.Latest - - topics := make([][][]byte, len(options.Topic)) - for i, topicDat := range options.Topic { - if slice, ok := topicDat.([]interface{}); ok { - topics[i] = make([][]byte, len(slice)) - for j, topic := range slice { - topics[i][j] = fromHex(topic.(string)) - } - } else if str, ok := topicDat.(string); ok { - topics[i] = make([][]byte, 1) - topics[i][0] = fromHex(str) - } + if err = json.Unmarshal(b, &obj); err != nil { + return errDecodeArgs } - opts.Topics = topics - return opts + if len(obj) < 1 { + return errArguments + } + args.Earliest = int64(ethutil.Big(obj[0].FromBlock).Int64()) + args.Latest = int64(ethutil.Big(obj[0].ToBlock).Int64()) + args.Max = int(ethutil.Big(obj[0].Limit).Int64()) + args.Skip = int(ethutil.Big(obj[0].Offset).Int64()) + args.Address = obj[0].Address + args.Topics = obj[0].Topics + + return nil } -type FilterChangedArgs struct { - n int -} +// type FilterChangedArgs struct { +// n int +// } type DbArgs struct { Database string @@ -248,12 +333,32 @@ type DbArgs struct { Value string } +func (args *DbArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + r := bytes.NewReader(b) + if err := json.NewDecoder(r).Decode(&obj); err != nil { + return errDecodeArgs + } + + if len(obj) < 2 { + return errArguments + } + args.Database = obj[0].(string) + args.Key = obj[1].(string) + + if len(obj) > 2 { + args.Value = obj[2].(string) + } + + return nil +} + func (a *DbArgs) requirements() error { if len(a.Database) == 0 { - return NewErrorWithMessage(errArguments, "DbPutArgs requires an 'Database' value as argument") + return NewErrorWithMessage(errArguments, "Database cannot be blank") } if len(a.Key) == 0 { - return NewErrorWithMessage(errArguments, "DbPutArgs requires an 'Key' value as argument") + return NewErrorWithMessage(errArguments, "Key cannot be blank") } return nil } @@ -262,7 +367,140 @@ type WhisperMessageArgs struct { Payload string To string From string - Topic []string + Topics []string Priority uint32 Ttl uint32 } + +func (args *WhisperMessageArgs) UnmarshalJSON(b []byte) (err error) { + var obj []struct { + Payload string + To string + From string + Topics []string + Priority string + Ttl string + } + + if err = json.Unmarshal(b, &obj); err != nil { + return errDecodeArgs + } + + if len(obj) < 1 { + return errArguments + } + args.Payload = obj[0].Payload + args.To = obj[0].To + args.From = obj[0].From + args.Topics = obj[0].Topics + args.Priority = uint32(ethutil.Big(obj[0].Priority).Int64()) + args.Ttl = uint32(ethutil.Big(obj[0].Ttl).Int64()) + + return nil +} + +type CompileArgs struct { + Source string +} + +func (args *CompileArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + r := bytes.NewReader(b) + if err := json.NewDecoder(r).Decode(&obj); err != nil { + return errDecodeArgs + } + + if len(obj) > 0 { + args.Source = obj[0].(string) + } + + return nil +} + +type FilterStringArgs struct { + Word string +} + +func (args *FilterStringArgs) UnmarshalJSON(b []byte) (err error) { + var obj []string + r := bytes.NewReader(b) + if err := json.NewDecoder(r).Decode(&obj); err != nil { + return errDecodeArgs + } + + if len(obj) < 1 { + return errDecodeArgs + } + + args.Word = obj[0] + + return nil +} + +type FilterIdArgs struct { + Id int +} + +func (args *FilterIdArgs) UnmarshalJSON(b []byte) (err error) { + var obj []string + r := bytes.NewReader(b) + if err := json.NewDecoder(r).Decode(&obj); err != nil { + return errDecodeArgs + } + + if len(obj) < 1 { + return errDecodeArgs + } + + args.Id = int(ethutil.Big(obj[0]).Int64()) + + return nil +} + +type WhisperIdentityArgs struct { + Identity string +} + +func (args *WhisperIdentityArgs) UnmarshalJSON(b []byte) (err error) { + var obj []string + r := bytes.NewReader(b) + if err := json.NewDecoder(r).Decode(&obj); err != nil { + return errDecodeArgs + } + + if len(obj) < 1 { + return errDecodeArgs + } + + args.Identity = obj[0] + + return nil +} + +type WhisperFilterArgs struct { + To string `json:"to"` + From string + Topics []string +} + +func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { + var obj []struct { + To string + From string + Topics []string + } + + if err = json.Unmarshal(b, &obj); err != nil { + return errDecodeArgs + } + + if len(obj) < 1 { + return errArguments + } + + args.To = obj[0].To + args.From = obj[0].From + args.Topics = obj[0].Topics + + return nil +} diff --git a/rpc/args_test.go b/rpc/args_test.go new file mode 100644 index 0000000000..0276245001 --- /dev/null +++ b/rpc/args_test.go @@ -0,0 +1,434 @@ +package rpc + +import ( + "bytes" + "encoding/json" + "math/big" + "testing" +) + +func TestSha3(t *testing.T) { + input := `["0x68656c6c6f20776f726c64"]` + expected := "0x68656c6c6f20776f726c64" + + args := new(Sha3Args) + json.Unmarshal([]byte(input), &args) + + if args.Data != expected { + t.Error("got %s expected %s", input, expected) + } +} + +func TestGetBalanceArgs(t *testing.T) { + input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "0x1f"]` + expected := new(GetBalanceArgs) + expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" + expected.BlockNumber = 31 + + args := new(GetBalanceArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if err := args.requirements(); err != nil { + t.Error(err) + } + + if args.Address != expected.Address { + t.Errorf("Address should be %v but is %v", expected.Address, args.Address) + } + + if args.BlockNumber != expected.BlockNumber { + t.Errorf("BlockNumber should be %v but is %v", expected.BlockNumber, args.BlockNumber) + } +} + +func TestGetBlockByHashArgs(t *testing.T) { + input := `["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", true]` + expected := new(GetBlockByHashArgs) + expected.BlockHash = "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" + expected.Transactions = true + + args := new(GetBlockByHashArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if args.BlockHash != expected.BlockHash { + t.Errorf("BlockHash should be %v but is %v", expected.BlockHash, args.BlockHash) + } + + if args.Transactions != expected.Transactions { + t.Errorf("Transactions should be %v but is %v", expected.Transactions, args.Transactions) + } +} + +func TestGetBlockByNumberArgs(t *testing.T) { + input := `["0x1b4", false]` + expected := new(GetBlockByNumberArgs) + expected.BlockNumber = 436 + expected.Transactions = false + + args := new(GetBlockByNumberArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if args.BlockNumber != expected.BlockNumber { + t.Errorf("BlockHash should be %v but is %v", expected.BlockNumber, args.BlockNumber) + } + + if args.Transactions != expected.Transactions { + t.Errorf("Transactions should be %v but is %v", expected.Transactions, args.Transactions) + } +} + +func TestNewTxArgs(t *testing.T) { + input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a000", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}]` + expected := new(NewTxArgs) + expected.From = "0xb60e8dd61c5d32be8058bb8eb970870f07233155" + expected.To = "0xd46e8dd67c5d32be8058bb8eb970870f072445675" + expected.Gas = big.NewInt(30400) + expected.GasPrice = big.NewInt(10000000000000) + expected.Value = big.NewInt(10000000000000) + expected.Data = "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + + args := new(NewTxArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.From != args.From { + t.Errorf("From shoud be %#v but is %#v", expected.From, args.From) + } + + if expected.To != args.To { + t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) + } + + if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { + t.Errorf("Gas shoud be %#v but is %#v", expected.Gas.Bytes(), args.Gas.Bytes()) + } + + if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { + t.Errorf("GasPrice shoud be %#v but is %#v", expected.GasPrice, args.GasPrice) + } + + if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { + t.Errorf("Value shoud be %#v but is %#v", expected.Value, args.Value) + } + + if expected.Data != args.Data { + t.Errorf("Data shoud be %#v but is %#v", expected.Data, args.Data) + } +} + +func TestGetStorageArgs(t *testing.T) { + input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]` + expected := new(GetStorageArgs) + expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" + expected.BlockNumber = -1 + + args := new(GetStorageArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if err := args.requirements(); err != nil { + t.Error(err) + } + + if expected.Address != args.Address { + t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) + } + + if expected.BlockNumber != args.BlockNumber { + t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) + } +} + +func TestGetStorageAtArgs(t *testing.T) { + input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "0x0", "0x2"]` + expected := new(GetStorageAtArgs) + expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" + expected.Key = "0x0" + expected.BlockNumber = 2 + + args := new(GetStorageAtArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if err := args.requirements(); err != nil { + t.Error(err) + } + + if expected.Address != args.Address { + t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) + } + + if expected.Key != args.Key { + t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) + } + + if expected.BlockNumber != args.BlockNumber { + t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) + } +} + +func TestGetTxCountArgs(t *testing.T) { + input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]` + expected := new(GetTxCountArgs) + expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" + expected.BlockNumber = -1 + + args := new(GetTxCountArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if err := args.requirements(); err != nil { + t.Error(err) + } + + if expected.Address != args.Address { + t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) + } + + if expected.BlockNumber != args.BlockNumber { + t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) + } +} + +func TestGetDataArgs(t *testing.T) { + input := `["0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", "latest"]` + expected := new(GetDataArgs) + expected.Address = "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8" + expected.BlockNumber = -1 + + args := new(GetDataArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if err := args.requirements(); err != nil { + t.Error(err) + } + + if expected.Address != args.Address { + t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) + } + + if expected.BlockNumber != args.BlockNumber { + t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) + } +} + +func TestFilterOptions(t *testing.T) { + input := `[{ + "fromBlock": "0x1", + "toBlock": "0x2", + "limit": "0x3", + "offset": "0x0", + "address": "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", + "topics": ["0x12341234"]}]` + expected := new(FilterOptions) + expected.Earliest = 1 + expected.Latest = 2 + expected.Max = 3 + expected.Skip = 0 + expected.Address = "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8" + // expected.Topics = []string{"0x12341234"} + + args := new(FilterOptions) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.Earliest != args.Earliest { + t.Errorf("Earliest shoud be %#v but is %#v", expected.Earliest, args.Earliest) + } + + if expected.Latest != args.Latest { + t.Errorf("Latest shoud be %#v but is %#v", expected.Latest, args.Latest) + } + + if expected.Max != args.Max { + t.Errorf("Max shoud be %#v but is %#v", expected.Max, args.Max) + } + + if expected.Skip != args.Skip { + t.Errorf("Skip shoud be %#v but is %#v", expected.Skip, args.Skip) + } + + if expected.Address != args.Address { + t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) + } + + // if expected.Topics != args.Topics { + // t.Errorf("Topic shoud be %#v but is %#v", expected.Topic, args.Topic) + // } +} + +func TestDbArgs(t *testing.T) { + input := `["0x74657374","0x6b6579","0x6d79537472696e67"]` + expected := new(DbArgs) + expected.Database = "0x74657374" + expected.Key = "0x6b6579" + expected.Value = "0x6d79537472696e67" + + args := new(DbArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if err := args.requirements(); err != nil { + t.Error(err) + } + + if expected.Database != args.Database { + t.Errorf("Database shoud be %#v but is %#v", expected.Database, args.Database) + } + + if expected.Key != args.Key { + t.Errorf("Key shoud be %#v but is %#v", expected.Key, args.Key) + } + + if expected.Value != args.Value { + t.Errorf("Value shoud be %#v but is %#v", expected.Value, args.Value) + } +} + +func TestWhisperMessageArgs(t *testing.T) { + input := `[{"from":"0xc931d93e97ab07fe42d923478ba2465f2", + "topics": ["0x68656c6c6f20776f726c64"], + "payload":"0x68656c6c6f20776f726c64", + "ttl": "0x64", + "priority": "0x64"}]` + expected := new(WhisperMessageArgs) + expected.From = "0xc931d93e97ab07fe42d923478ba2465f2" + expected.To = "" + expected.Payload = "0x68656c6c6f20776f726c64" + expected.Priority = 100 + expected.Ttl = 100 + expected.Topics = []string{"0x68656c6c6f20776f726c64"} + + args := new(WhisperMessageArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.From != args.From { + t.Errorf("From shoud be %#v but is %#v", expected.From, args.From) + } + + if expected.To != args.To { + t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) + } + + if expected.Payload != args.Payload { + t.Errorf("Value shoud be %#v but is %#v", expected.Payload, args.Payload) + } + + if expected.Ttl != args.Ttl { + t.Errorf("Ttl shoud be %#v but is %#v", expected.Ttl, args.Ttl) + } + + if expected.Priority != args.Priority { + t.Errorf("Priority shoud be %#v but is %#v", expected.Priority, args.Priority) + } + + // if expected.Topics != args.Topics { + // t.Errorf("Topic shoud be %#v but is %#v", expected.Topic, args.Topic) + // } +} + +func TestFilterIdArgs(t *testing.T) { + input := `["0x7"]` + expected := new(FilterIdArgs) + expected.Id = 7 + + args := new(FilterIdArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.Id != args.Id { + t.Errorf("Id shoud be %#v but is %#v", expected.Id, args.Id) + } +} + +func TestWhsiperFilterArgs(t *testing.T) { + input := `[{"topics": ["0x68656c6c6f20776f726c64"], "to": "0x34ag445g3455b34"}]` + expected := new(WhisperFilterArgs) + expected.From = "" + expected.To = "0x34ag445g3455b34" + expected.Topics = []string{"0x68656c6c6f20776f726c64"} + + args := new(WhisperFilterArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.From != args.From { + t.Errorf("From shoud be %#v but is %#v", expected.From, args.From) + } + + if expected.To != args.To { + t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) + } + + // if expected.Topics != args.Topics { + // t.Errorf("Topics shoud be %#v but is %#v", expected.Topics, args.Topics) + // } +} + +func TestCompileArgs(t *testing.T) { + input := `["contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"]` + expected := new(CompileArgs) + expected.Source = `contract test { function multiply(uint a) returns(uint d) { return a * 7; } }` + + args := new(CompileArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.Source != args.Source { + t.Errorf("Source shoud be %#v but is %#v", expected.Source, args.Source) + } +} + +func TestFilterStringArgs(t *testing.T) { + input := `["pending"]` + expected := new(FilterStringArgs) + expected.Word = "pending" + + args := new(FilterStringArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.Word != args.Word { + t.Errorf("Word shoud be %#v but is %#v", expected.Word, args.Word) + } +} + +func TestWhisperIdentityArgs(t *testing.T) { + input := `["0xc931d93e97ab07fe42d923478ba2465f283"]` + expected := new(WhisperIdentityArgs) + expected.Identity = "0xc931d93e97ab07fe42d923478ba2465f283" + + args := new(WhisperIdentityArgs) + if err := json.Unmarshal([]byte(input), &args); err != nil { + t.Error(err) + } + + if expected.Identity != args.Identity { + t.Errorf("Identity shoud be %#v but is %#v", expected.Identity, args.Identity) + } +} diff --git a/rpc/http.go b/rpc/http.go new file mode 100644 index 0000000000..857cf32210 --- /dev/null +++ b/rpc/http.go @@ -0,0 +1,52 @@ +package rpc + +import ( + "net/http" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/xeth" +) + +var rpchttplogger = logger.NewLogger("RPC-HTTP") + +const ( + jsonrpcver = "2.0" + maxSizeReqLength = 1024 * 1024 // 1MB +) + +// JSONRPC returns a handler that implements the Ethereum JSON-RPC API. +func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { + var json JsonWrapper + api := NewEthereumApi(pipe, dataDir) + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + + rpchttplogger.DebugDetailln("Handling request") + + if req.ContentLength > maxSizeReqLength { + jsonerr := &RpcErrorObject{-32700, "Error: Request too large"} + json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + return + } + + reqParsed, reqerr := json.ParseRequestBody(req) + if reqerr != nil { + jsonerr := &RpcErrorObject{-32700, "Error: Could not parse request"} + json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + return + } + + var response interface{} + reserr := api.GetRequestReply(&reqParsed, &response) + if reserr != nil { + rpchttplogger.Warnln(reserr) + jsonerr := &RpcErrorObject{-32603, reserr.Error()} + json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) + return + } + + rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) + json.Send(w, &RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response}) + }) +} diff --git a/rpc/http/server.go b/rpc/http/server.go deleted file mode 100644 index d8f0157f11..0000000000 --- a/rpc/http/server.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - go-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with go-ethereum. If not, see . -*/ -package rpchttp - -import ( - "fmt" - "net" - "net/http" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/xeth" -) - -var rpchttplogger = logger.NewLogger("RPC-HTTP") -var JSON rpc.JsonWrapper - -const maxSizeReqLength = 1024 * 1024 // 1MB - -func NewRpcHttpServer(pipe *xeth.XEth, address string, port int) (*RpcHttpServer, error) { - sport := fmt.Sprintf("%s:%d", address, port) - l, err := net.Listen("tcp", sport) - if err != nil { - return nil, err - } - - return &RpcHttpServer{ - listener: l, - quit: make(chan bool), - pipe: pipe, - port: port, - addr: address, - }, nil -} - -type RpcHttpServer struct { - quit chan bool - listener net.Listener - pipe *xeth.XEth - port int - addr string -} - -func (s *RpcHttpServer) exitHandler() { -out: - for { - select { - case <-s.quit: - s.listener.Close() - break out - } - } - - rpchttplogger.Infoln("Shutdown RPC-HTTP server") -} - -func (s *RpcHttpServer) Stop() { - close(s.quit) -} - -func (s *RpcHttpServer) Start() { - rpchttplogger.Infof("Starting RPC-HTTP server on %s:%d", s.addr, s.port) - go s.exitHandler() - - api := rpc.NewEthereumApi(s.pipe) - h := s.apiHandler(api) - http.Handle("/", h) - - err := http.Serve(s.listener, nil) - // FIX Complains on shutdown due to listner already being closed - if err != nil { - rpchttplogger.Errorln("Error on RPC-HTTP interface:", err) - } -} - -func (s *RpcHttpServer) apiHandler(api *rpc.EthereumApi) http.Handler { - var jsonrpcver string = "2.0" - fn := func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - - rpchttplogger.DebugDetailln("Handling request") - - if req.ContentLength > maxSizeReqLength { - jsonerr := &rpc.RpcErrorObject{-32700, "Error: Request too large"} - JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) - return - } - - reqParsed, reqerr := JSON.ParseRequestBody(req) - if reqerr != nil { - jsonerr := &rpc.RpcErrorObject{-32700, "Error: Could not parse request"} - JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) - return - } - - var response interface{} - reserr := api.GetRequestReply(&reqParsed, &response) - if reserr != nil { - rpchttplogger.Warnln(reserr) - jsonerr := &rpc.RpcErrorObject{-32603, reserr.Error()} - JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) - return - } - - rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) - JSON.Send(w, &rpc.RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response}) - } - - return http.HandlerFunc(fn) -} diff --git a/rpc/messages.go b/rpc/messages.go index b37d8229d6..a3ebbf330c 100644 --- a/rpc/messages.go +++ b/rpc/messages.go @@ -17,12 +17,9 @@ package rpc import ( - "bytes" "encoding/json" "errors" "fmt" - - "github.com/ethereum/go-ethereum/xeth" ) var ( @@ -33,10 +30,10 @@ var ( ) type RpcRequest struct { - ID interface{} `json:"id"` - JsonRpc string `json:"jsonrpc"` - Method string `json:"method"` - Params []json.RawMessage `json:"params"` + ID interface{} `json:"id"` + JsonRpc string `json:"jsonrpc"` + Method string `json:"method"` + Params json.RawMessage `json:"params"` } type RpcSuccessResponse struct { @@ -61,359 +58,30 @@ func NewErrorWithMessage(err error, msg string) error { return fmt.Errorf("%s: %s", err.Error(), msg) } -func (req *RpcRequest) ToSha3Args() (*Sha3Args, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(Sha3Args) - r := bytes.NewReader(req.Params[0]) - if err := json.NewDecoder(r).Decode(args); err != nil { - return nil, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToGetBlockArgs() (*GetBlockArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(GetBlockArgs) - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToNewTxArgs() (*NewTxArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(NewTxArgs) - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) - } - - return args, nil -} - -func (req *RpcRequest) ToPushTxArgs() (*PushTxArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(PushTxArgs) - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToGetStateArgs() (*GetStateArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(GetStateArgs) - // TODO need to pass both arguments - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToStorageAtArgs() (*GetStorageArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(GetStorageArgs) - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToGetTxCountArgs() (*GetTxCountArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(GetTxCountArgs) - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToGetBalanceArgs() (*GetBalanceArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(GetBalanceArgs) - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToGetCodeAtArgs() (*GetCodeAtArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(GetCodeAtArgs) - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToBoolArgs() (bool, error) { - if len(req.Params) < 1 { - return false, errArguments - } - - var args bool - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return false, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToIntArgs() (int, error) { - if len(req.Params) < 1 { - return 0, errArguments - } - - var args int - if err := json.Unmarshal(req.Params[0], &args); err != nil { - return 0, errArguments - } - - return args, nil -} - -func (req *RpcRequest) ToCompileArgs() (string, error) { - if len(req.Params) < 1 { - return "", errArguments - } - - var args string - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return "", errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToFilterArgs() (*FilterOptions, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - args := new(FilterOptions) - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(args) - if err != nil { - return nil, errDecodeArgs - } - return args, nil -} - -func (req *RpcRequest) ToFilterStringArgs() (string, error) { - if len(req.Params) < 1 { - return "", errArguments - } - - var args string - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return "", errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToUninstallFilterArgs() (int, error) { - if len(req.Params) < 1 { - return 0, errArguments - } - - var args int - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return 0, errDecodeArgs - } - - return args, nil -} - -func (req *RpcRequest) ToFilterChangedArgs() (int, error) { - if len(req.Params) < 1 { - return 0, errArguments - } - - var id int - r := bytes.NewReader(req.Params[0]) - err := json.NewDecoder(r).Decode(&id) - if err != nil { - return 0, errDecodeArgs - } - return id, nil -} - -func (req *RpcRequest) ToDbPutArgs() (*DbArgs, error) { - if len(req.Params) < 3 { - return nil, errArguments - } - - var args DbArgs - err := json.Unmarshal(req.Params[0], &args.Database) - if err != nil { - return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) - } - err = json.Unmarshal(req.Params[1], &args.Key) - if err != nil { - return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) - } - err = json.Unmarshal(req.Params[2], &args.Value) - if err != nil { - return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) - } - - return &args, nil -} - -func (req *RpcRequest) ToDbGetArgs() (*DbArgs, error) { - if len(req.Params) < 2 { - return nil, errArguments - } - - var args DbArgs - err := json.Unmarshal(req.Params[0], &args.Database) - if err != nil { - return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) - } - - err = json.Unmarshal(req.Params[1], &args.Key) - if err != nil { - return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) - } - - return &args, nil -} - -func (req *RpcRequest) ToWhisperFilterArgs() (*xeth.Options, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - var args xeth.Options - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) - } - - return &args, nil -} - -func (req *RpcRequest) ToIdArgs() (int, error) { - if len(req.Params) < 1 { - return 0, errArguments - } - - var id int - err := json.Unmarshal(req.Params[0], &id) - if err != nil { - return 0, errDecodeArgs - } - - return id, nil -} - -func (req *RpcRequest) ToWhisperPostArgs() (*WhisperMessageArgs, error) { - if len(req.Params) < 1 { - return nil, errArguments - } - - var args WhisperMessageArgs - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return nil, err - } - - return &args, nil -} - -func (req *RpcRequest) ToWhisperHasIdentityArgs() (string, error) { - if len(req.Params) < 1 { - return "", errArguments - } - - var args string - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return "", err - } - - return args, nil -} - -func (req *RpcRequest) ToRegisterArgs() (string, error) { - if len(req.Params) < 1 { - return "", errArguments - } - - var args string - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return "", err - } - - return args, nil -} - -func (req *RpcRequest) ToWatchTxArgs() (string, error) { - if len(req.Params) < 1 { - return "", errArguments - } - - var args string - err := json.Unmarshal(req.Params[0], &args) - if err != nil { - return "", err - } - - return args, nil -} +// func (req *RpcRequest) ToRegisterArgs() (string, error) { +// if len(req.Params) < 1 { +// return "", errArguments +// } + +// var args string +// err := json.Unmarshal(req.Params, &args) +// if err != nil { +// return "", err +// } + +// return args, nil +// } + +// func (req *RpcRequest) ToWatchTxArgs() (string, error) { +// if len(req.Params) < 1 { +// return "", errArguments +// } + +// var args string +// err := json.Unmarshal(req.Params, &args) +// if err != nil { +// return "", err +// } + +// return args, nil +// } diff --git a/rpc/responses.go b/rpc/responses.go new file mode 100644 index 0000000000..f41ce7b968 --- /dev/null +++ b/rpc/responses.go @@ -0,0 +1,212 @@ +package rpc + +import ( + "encoding/json" + // "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/core/types" +) + +type BlockRes struct { + fullTx bool + + BlockNumber int64 `json:"number"` + BlockHash []byte `json:"hash"` + ParentHash []byte `json:"parentHash"` + Nonce []byte `json:"nonce"` + Sha3Uncles []byte `json:"sha3Uncles"` + LogsBloom []byte `json:"logsBloom"` + TransactionRoot []byte `json:"transactionRoot"` + StateRoot []byte `json:"stateRoot"` + Miner []byte `json:"miner"` + Difficulty int64 `json:"difficulty"` + TotalDifficulty int64 `json:"totalDifficulty"` + Size int64 `json:"size"` + ExtraData []byte `json:"extraData"` + GasLimit int64 `json:"gasLimit"` + MinGasPrice int64 `json:"minGasPrice"` + GasUsed int64 `json:"gasUsed"` + UnixTimestamp int64 `json:"timestamp"` + Transactions []*TransactionRes `json:"transactions"` + Uncles [][]byte `json:"uncles"` +} + +func (b *BlockRes) MarshalJSON() ([]byte, error) { + var ext struct { + BlockNumber string `json:"number"` + BlockHash string `json:"hash"` + ParentHash string `json:"parentHash"` + Nonce string `json:"nonce"` + Sha3Uncles string `json:"sha3Uncles"` + LogsBloom string `json:"logsBloom"` + TransactionRoot string `json:"transactionRoot"` + StateRoot string `json:"stateRoot"` + Miner string `json:"miner"` + Difficulty string `json:"difficulty"` + TotalDifficulty string `json:"totalDifficulty"` + Size string `json:"size"` + ExtraData string `json:"extraData"` + GasLimit string `json:"gasLimit"` + MinGasPrice string `json:"minGasPrice"` + GasUsed string `json:"gasUsed"` + UnixTimestamp string `json:"timestamp"` + Transactions []interface{} `json:"transactions"` + Uncles []string `json:"uncles"` + } + + // convert strict types to hexified strings + ext.BlockNumber = toHex(big.NewInt(b.BlockNumber).Bytes()) + ext.BlockHash = toHex(b.BlockHash) + ext.ParentHash = toHex(b.ParentHash) + ext.Nonce = toHex(b.Nonce) + ext.Sha3Uncles = toHex(b.Sha3Uncles) + ext.LogsBloom = toHex(b.LogsBloom) + ext.TransactionRoot = toHex(b.TransactionRoot) + ext.StateRoot = toHex(b.StateRoot) + ext.Miner = toHex(b.Miner) + ext.Difficulty = toHex(big.NewInt(b.Difficulty).Bytes()) + ext.TotalDifficulty = toHex(big.NewInt(b.TotalDifficulty).Bytes()) + ext.Size = toHex(big.NewInt(b.Size).Bytes()) + // ext.ExtraData = toHex(b.ExtraData) + ext.GasLimit = toHex(big.NewInt(b.GasLimit).Bytes()) + // ext.MinGasPrice = toHex(big.NewInt(b.MinGasPrice).Bytes()) + ext.GasUsed = toHex(big.NewInt(b.GasUsed).Bytes()) + ext.UnixTimestamp = toHex(big.NewInt(b.UnixTimestamp).Bytes()) + ext.Transactions = make([]interface{}, len(b.Transactions)) + if b.fullTx { + for i, tx := range b.Transactions { + ext.Transactions[i] = tx + } + } else { + for i, tx := range b.Transactions { + ext.Transactions[i] = toHex(tx.Hash) + } + } + ext.Uncles = make([]string, len(b.Uncles)) + for i, v := range b.Uncles { + ext.Uncles[i] = toHex(v) + } + + return json.Marshal(ext) +} + +func NewBlockRes(block *types.Block) *BlockRes { + if block == nil { + return &BlockRes{} + } + + res := new(BlockRes) + res.BlockNumber = block.Number().Int64() + res.BlockHash = block.Hash() + res.ParentHash = block.ParentHash() + res.Nonce = block.Header().Nonce + res.Sha3Uncles = block.Header().UncleHash + res.LogsBloom = block.Bloom() + res.TransactionRoot = block.Header().TxHash + res.StateRoot = block.Root() + res.Miner = block.Header().Coinbase + res.Difficulty = block.Difficulty().Int64() + if block.Td != nil { + res.TotalDifficulty = block.Td.Int64() + } + res.Size = int64(block.Size()) + // res.ExtraData = + res.GasLimit = block.GasLimit().Int64() + // res.MinGasPrice = + res.GasUsed = block.GasUsed().Int64() + res.UnixTimestamp = block.Time() + res.Transactions = make([]*TransactionRes, len(block.Transactions())) + for i, tx := range block.Transactions() { + v := NewTransactionRes(tx) + v.BlockHash = block.Hash() + v.BlockNumber = block.Number().Int64() + v.TxIndex = int64(i) + res.Transactions[i] = v + } + res.Uncles = make([][]byte, len(block.Uncles())) + for i, uncle := range block.Uncles() { + res.Uncles[i] = uncle.Hash() + } + return res +} + +type TransactionRes struct { + Hash []byte `json:"hash"` + Nonce int64 `json:"nonce"` + BlockHash []byte `json:"blockHash,omitempty"` + BlockNumber int64 `json:"blockNumber,omitempty"` + TxIndex int64 `json:"transactionIndex,omitempty"` + From []byte `json:"from"` + To []byte `json:"to"` + Value int64 `json:"value"` + Gas int64 `json:"gas"` + GasPrice int64 `json:"gasPrice"` + Input []byte `json:"input"` +} + +func (t *TransactionRes) MarshalJSON() ([]byte, error) { + var ext struct { + Hash string `json:"hash"` + Nonce string `json:"nonce"` + BlockHash string `json:"blockHash,omitempty"` + BlockNumber string `json:"blockNumber,omitempty"` + TxIndex string `json:"transactionIndex,omitempty"` + From string `json:"from"` + To string `json:"to"` + Value string `json:"value"` + Gas string `json:"gas"` + GasPrice string `json:"gasPrice"` + Input string `json:"input"` + } + + ext.Hash = toHex(t.Hash) + ext.Nonce = toHex(big.NewInt(t.Nonce).Bytes()) + ext.BlockHash = toHex(t.BlockHash) + ext.BlockNumber = toHex(big.NewInt(t.BlockNumber).Bytes()) + ext.TxIndex = toHex(big.NewInt(t.TxIndex).Bytes()) + ext.From = toHex(t.From) + ext.To = toHex(t.To) + ext.Value = toHex(big.NewInt(t.Value).Bytes()) + ext.Gas = toHex(big.NewInt(t.Gas).Bytes()) + ext.GasPrice = toHex(big.NewInt(t.GasPrice).Bytes()) + ext.Input = toHex(t.Input) + + return json.Marshal(ext) +} + +func NewTransactionRes(tx *types.Transaction) *TransactionRes { + var v = new(TransactionRes) + v.Hash = tx.Hash() + v.Nonce = int64(tx.Nonce()) + v.From = tx.From() + v.To = tx.To() + v.Value = tx.Value().Int64() + v.Gas = tx.Gas().Int64() + v.GasPrice = tx.GasPrice().Int64() + v.Input = tx.Data() + return v +} + +type FilterLogRes struct { + Hash string `json:"hash"` + Address string `json:"address"` + Data string `json:"data"` + BlockNumber string `json:"blockNumber"` + TransactionHash string `json:"transactionHash"` + BlockHash string `json:"blockHash"` + TransactionIndex string `json:"transactionIndex"` + LogIndex string `json:"logIndex"` +} + +type FilterWhisperRes struct { + Hash string `json:"hash"` + From string `json:"from"` + To string `json:"to"` + Expiry string `json:"expiry"` + Sent string `json:"sent"` + Ttl string `json:"ttl"` + Topics string `json:"topics"` + Payload string `json:"payload"` + WorkProved string `json:"workProved"` +} diff --git a/rpc/util.go b/rpc/util.go index 3e8ca3fef0..8ff3c6d315 100644 --- a/rpc/util.go +++ b/rpc/util.go @@ -18,8 +18,11 @@ package rpc import ( "encoding/json" + "fmt" "io" + "math/big" "net/http" + "reflect" "time" "github.com/ethereum/go-ethereum/ethutil" @@ -32,9 +35,63 @@ var rpclogger = logger.NewLogger("RPC") type JsonWrapper struct{} +// Unmarshal state is a helper method which has the ability to decode messsages +// that use the `defaultBlock` (https://github.com/ethereum/wiki/wiki/JSON-RPC#the-default-block-parameter) +// For example a `call`: [{to: "0x....", data:"0x..."}, "latest"]. The first argument is the transaction +// message and the second one refers to the block height (or state) to which to apply this `call`. +func UnmarshalRawMessages(b []byte, iface interface{}, number *int64) (err error) { + var data []json.RawMessage + if err = json.Unmarshal(b, &data); err != nil && len(data) == 0 { + return errDecodeArgs + } + + // Number index determines the index in the array for a possible block number + numberIndex := 0 + + value := reflect.ValueOf(iface) + rvalue := reflect.Indirect(value) + + switch rvalue.Kind() { + case reflect.Slice: + // This is a bit of a cheat, but `data` is expected to be larger than 2 if iface is a slice + if number != nil { + numberIndex = len(data) - 1 + } else { + numberIndex = len(data) + } + + slice := reflect.MakeSlice(rvalue.Type(), numberIndex, numberIndex) + for i, raw := range data[0:numberIndex] { + v := slice.Index(i).Interface() + if err = json.Unmarshal(raw, &v); err != nil { + fmt.Println(err, v) + return err + } + slice.Index(i).Set(reflect.ValueOf(v)) + } + reflect.Indirect(rvalue).Set(slice) //value.Set(slice) + case reflect.Struct: + fallthrough + default: + if err = json.Unmarshal(data[0], iface); err != nil { + return errDecodeArgs + } + numberIndex = 1 + } + + // <0 index means out of bound for block number + if numberIndex >= 0 && len(data) > numberIndex { + if err = blockNumber(data[numberIndex], number); err != nil { + return errDecodeArgs + } + } + + return nil +} + func (self JsonWrapper) Send(writer io.Writer, v interface{}) (n int, err error) { var payload []byte - payload, err = json.Marshal(v) + payload, err = json.MarshalIndent(v, "", "\t") if err != nil { rpclogger.Fatalln("Error marshalling JSON", err) return 0, err @@ -63,18 +120,31 @@ func (self JsonWrapper) ParseRequestBody(req *http.Request) (RpcRequest, error) } func toHex(b []byte) string { - return "0x" + ethutil.Bytes2Hex(b) + hex := ethutil.Bytes2Hex(b) + // Prefer output of "0x0" instead of "0x" + if len(hex) == 0 { + hex = "0" + } + return "0x" + hex } + func fromHex(s string) []byte { if len(s) > 1 { if s[0:2] == "0x" { s = s[2:] } + if len(s)%2 == 1 { + s = "0" + s + } return ethutil.Hex2Bytes(s) } return nil } +func i2hex(n int) string { + return toHex(big.NewInt(int64(n)).Bytes()) +} + type RpcServer interface { Start() Stop() diff --git a/rpc/util_test.go b/rpc/util_test.go new file mode 100644 index 0000000000..b0a4979b5a --- /dev/null +++ b/rpc/util_test.go @@ -0,0 +1,25 @@ +package rpc + +import ( + "bytes" + "testing" +) + +//fromHex +func TestFromHex(t *testing.T) { + input := "0x01" + expected := []byte{1} + result := fromHex(input) + if bytes.Compare(expected, result) != 0 { + t.Errorf("Expected % x got % x", expected, result) + } +} + +func TestFromHexOddLength(t *testing.T) { + input := "0x1" + expected := []byte{1} + result := fromHex(input) + if bytes.Compare(expected, result) != 0 { + t.Errorf("Expected % x got % x", expected, result) + } +} diff --git a/state/state_test.go b/state/state_test.go index ee1cf92866..07e35f7e28 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -33,16 +33,16 @@ func (s *StateSuite) TestDump(c *checker.C) { // check that dump contains the state objects that are in trie got := string(s.state.Dump()) want := `{ - "root": "4e3a59299745ba6752247c8b91d0f716dac9ec235861c91f5ac1894a361d87ba", + "root": "6e277ae8357d013e50f74eedb66a991f6922f93ae03714de58b3d0c5e9eee53f", "accounts": { - "0000000000000000000000000000000000000001": { + "1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d": { "balance": "22", "nonce": 0, "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "storage": {} }, - "0000000000000000000000000000000000000102": { + "a17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1": { "balance": "0", "nonce": 0, "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", @@ -57,7 +57,6 @@ func (s *StateSuite) TestDump(c *checker.C) { } func (s *StateSuite) SetUpTest(c *checker.C) { - ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "") db, _ := ethdb.NewMemDatabase() s.state = New(nil, db) } diff --git a/tests/helper/init.go b/tests/helper/init.go index df98b9e422..e0892d8f69 100644 --- a/tests/helper/init.go +++ b/tests/helper/init.go @@ -4,7 +4,6 @@ import ( "log" "os" - "github.com/ethereum/go-ethereum/ethutil" logpkg "github.com/ethereum/go-ethereum/logger" ) @@ -14,6 +13,4 @@ var Log = logpkg.NewLogger("TEST") func init() { Logger = logpkg.NewStdLogSystem(os.Stdout, log.LstdFlags, logpkg.InfoLevel) logpkg.AddLogSystem(Logger) - - ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "") } diff --git a/ui/frontend.go b/ui/frontend.go deleted file mode 100644 index 413a242597..0000000000 --- a/ui/frontend.go +++ /dev/null @@ -1,8 +0,0 @@ -package ui - -import "github.com/ethereum/go-ethereum/core/types" - -type Interface interface { - UnlockAccount(address []byte) bool - ConfirmTransaction(tx *types.Transaction) bool -} diff --git a/vm/debugger.go b/vm/debugger.go deleted file mode 100644 index 830a8f69d9..0000000000 --- a/vm/debugger.go +++ /dev/null @@ -1 +0,0 @@ -package vm diff --git a/vm/vm.go b/vm/vm.go index 6f39454721..6e4a54844f 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -37,7 +37,7 @@ func (self *Vm) Run(me, caller ContextRef, code []byte, value, gas, price *big.I context := NewContext(caller, me, code, gas, price) - vmlogger.Debugf("(%d) (%x) %x (code=%d) gas: %v (d) %x\n", self.env.Depth(), caller.Address()[:4], context.Address(), len(code), context.Gas, callData) + self.Printf("(%d) (%x) %x (code=%d) gas: %v (d) %x", self.env.Depth(), caller.Address()[:4], context.Address(), len(code), context.Gas, callData).Endl() if self.Recoverable { // Recover from any require exception @@ -696,7 +696,7 @@ func (self *Vm) Run(me, caller ContextRef, code []byte, value, gas, price *big.I if err != nil { stack.push(ethutil.BigFalse) - vmlogger.Debugln(err) + self.Printf("%v").Endl() } else { stack.push(ethutil.BigTrue) @@ -726,7 +726,7 @@ func (self *Vm) Run(me, caller ContextRef, code []byte, value, gas, price *big.I return context.Return(nil), nil default: - vmlogger.Debugf("(pc) %-3v Invalid opcode %x\n", pc, op) + self.Printf("(pc) %-3v Invalid opcode %x\n", pc, op).Endl() panic(fmt.Errorf("Invalid opcode %x", op)) } @@ -894,7 +894,7 @@ func (self *Vm) Printf(format string, v ...interface{}) VirtualMachine { func (self *Vm) Endl() VirtualMachine { if self.debug { if self.logTy == LogTyPretty { - vmlogger.Debugln(self.logStr) + vmlogger.Infoln(self.logStr) self.logStr = "" } } diff --git a/whisper/peer.go b/whisper/peer.go index 66cfec88c4..4bd9428f5f 100644 --- a/whisper/peer.go +++ b/whisper/peer.go @@ -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) } diff --git a/xeth/xeth.go b/xeth/xeth.go index 88ae253cd6..d8dd66aeca 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -1,14 +1,13 @@ +// eXtended ETHereum package xeth -/* - * eXtended ETHereum - */ - import ( "bytes" "encoding/json" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -18,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/state" - "github.com/ethereum/go-ethereum/ui" "github.com/ethereum/go-ethereum/whisper" ) @@ -28,11 +26,11 @@ var pipelogger = logger.NewLogger("XETH") type Backend interface { BlockProcessor() *core.BlockProcessor ChainManager() *core.ChainManager + AccountManager() *accounts.Manager TxPool() *core.TxPool PeerCount() int IsListening() bool Peers() []*p2p.Peer - KeyManager() *crypto.KeyManager BlockDb() ethutil.Database StateDb() ethutil.Database EventMux() *event.TypeMux @@ -40,42 +38,67 @@ 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 } func (self *XEth) Backend() Backend { return self.eth } -func (self *XEth) UseState(statedb *state.StateDB) *XEth { +func (self *XEth) WithState(statedb *state.StateDB) *XEth { xeth := &XEth{ eth: self.eth, blockProcessor: self.blockProcessor, @@ -99,7 +122,14 @@ func (self *XEth) BlockByHash(strHash string) *Block { return NewBlock(block) } -func (self *XEth) BlockByNumber(num int32) *Block { +func (self *XEth) EthBlockByHash(strHash string) *types.Block { + hash := fromHex(strHash) + block := self.chainManager.GetBlock(hash) + + return block +} + +func (self *XEth) BlockByNumber(num int64) *Block { if num == -1 { return NewBlock(self.chainManager.CurrentBlock()) } @@ -107,20 +137,34 @@ func (self *XEth) BlockByNumber(num int32) *Block { return NewBlock(self.chainManager.GetBlockByNumber(uint64(num))) } +func (self *XEth) EthBlockByNumber(num int64) *types.Block { + if num == -1 { + return self.chainManager.CurrentBlock() + } + + return self.chainManager.GetBlockByNumber(uint64(num)) +} + func (self *XEth) Block(v interface{}) *Block { if n, ok := v.(int32); ok { - return self.BlockByNumber(n) + return self.BlockByNumber(int64(n)) } else if str, ok := v.(string); ok { return self.BlockByHash(str) } else if f, ok := v.(float64); ok { // Don't ask ... - return self.BlockByNumber(int32(f)) + return self.BlockByNumber(int64(f)) } return nil } func (self *XEth) Accounts() []string { - return []string{toHex(self.eth.KeyManager().Address())} + // TODO: check err? + accounts, _ := self.eth.AccountManager().Accounts() + accountAddresses := make([]string, len(accounts)) + for i, ac := range accounts { + accountAddresses[i] = toHex(ac.Address) + } + return accountAddresses } func (self *XEth) PeerCount() int { @@ -147,7 +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 { @@ -248,49 +293,48 @@ 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) { - if len(gasStr) == 0 { - gasStr = "100000" +var ( + defaultGasPrice = big.NewInt(10000000000000) + defaultGas = big.NewInt(90000) +) + +func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + 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), } - if len(gasPriceStr) == 0 { - gasPriceStr = "1" + if msg.gas.Cmp(big.NewInt(0)) == 0 { + msg.gas = defaultGas } - 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) - ) + if msg.gasPrice.Cmp(big.NewInt(0)) == 0 { + msg.gasPrice = defaultGasPrice + } - msg := types.NewTransactionMessage(fromHex(toStr), value, gas, price, data) - msg.Sign(key.PrivateKey) + block := self.chainManager.CurrentBlock() vmenv := core.NewEnv(statedb, self.chainManager, msg, block) - res, err := vmenv.Call(from, to.Address(), data, gas, price, value) - if err != nil { - return "", err - } - - return toHex(res), nil + res, err := vmenv.Call(msg.from, msg.to, msg.data, msg.gas, msg.gasPrice, msg.value) + return toHex(res), err } -func (self *XEth) Transact(toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { +func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { var ( + from []byte to []byte value = ethutil.NewValue(valueStr) gas = ethutil.NewValue(gasStr) price = ethutil.NewValue(gasPriceStr) data []byte - key = self.eth.KeyManager().KeyPair() contractCreation bool ) + from = fromHex(fromStr) data = fromHex(codeStr) to = fromHex(toStr) if len(to) == 0 { @@ -304,25 +348,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 }