Merge branch 'release/0.9.20'

This commit is contained in:
obscuren 2015-05-12 19:05:33 +02:00
commit 8e24378cc1
54 changed files with 4667 additions and 631 deletions

View File

@ -33,7 +33,6 @@ and accounts persistence is derived from stored keys' addresses
package accounts package accounts
import ( import (
"bytes"
"crypto/ecdsa" "crypto/ecdsa"
crand "crypto/rand" crand "crypto/rand"
"errors" "errors"
@ -41,6 +40,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
@ -50,12 +50,12 @@ var (
) )
type Account struct { type Account struct {
Address []byte Address common.Address
} }
type Manager struct { type Manager struct {
keyStore crypto.KeyStore2 keyStore crypto.KeyStore2
unlocked map[string]*unlocked unlocked map[common.Address]*unlocked
mutex sync.RWMutex mutex sync.RWMutex
} }
@ -67,40 +67,40 @@ type unlocked struct {
func NewManager(keyStore crypto.KeyStore2) *Manager { func NewManager(keyStore crypto.KeyStore2) *Manager {
return &Manager{ return &Manager{
keyStore: keyStore, keyStore: keyStore,
unlocked: make(map[string]*unlocked), unlocked: make(map[common.Address]*unlocked),
} }
} }
func (am *Manager) HasAccount(addr []byte) bool { func (am *Manager) HasAccount(addr common.Address) bool {
accounts, _ := am.Accounts() accounts, _ := am.Accounts()
for _, acct := range accounts { for _, acct := range accounts {
if bytes.Compare(acct.Address, addr) == 0 { if acct.Address == addr {
return true return true
} }
} }
return false return false
} }
func (am *Manager) Primary() (addr []byte, err error) { func (am *Manager) Primary() (addr common.Address, err error) {
addrs, err := am.keyStore.GetKeyAddresses() addrs, err := am.keyStore.GetKeyAddresses()
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, ErrNoKeys return common.Address{}, ErrNoKeys
} else if err != nil { } else if err != nil {
return nil, err return common.Address{}, err
} }
if len(addrs) == 0 { if len(addrs) == 0 {
return nil, ErrNoKeys return common.Address{}, ErrNoKeys
} }
return addrs[0], nil return addrs[0], nil
} }
func (am *Manager) DeleteAccount(address []byte, auth string) error { func (am *Manager) DeleteAccount(address common.Address, auth string) error {
return am.keyStore.DeleteKey(address, auth) return am.keyStore.DeleteKey(address, auth)
} }
func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) { func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
am.mutex.RLock() am.mutex.RLock()
unlockedKey, found := am.unlocked[string(a.Address)] unlockedKey, found := am.unlocked[a.Address]
am.mutex.RUnlock() am.mutex.RUnlock()
if !found { if !found {
return nil, ErrLocked return nil, ErrLocked
@ -111,7 +111,7 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error)
// TimedUnlock unlocks the account with the given address. // TimedUnlock unlocks the account with the given address.
// When timeout has passed, the account will be locked again. // When timeout has passed, the account will be locked again.
func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error { func (am *Manager) TimedUnlock(addr common.Address, keyAuth string, timeout time.Duration) error {
key, err := am.keyStore.GetKey(addr, keyAuth) key, err := am.keyStore.GetKey(addr, keyAuth)
if err != nil { if err != nil {
return err return err
@ -124,7 +124,7 @@ func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duratio
// Unlock unlocks the account with the given address. The account // Unlock unlocks the account with the given address. The account
// stays unlocked until the program exits or until a TimedUnlock // stays unlocked until the program exits or until a TimedUnlock
// timeout (started after the call to Unlock) expires. // timeout (started after the call to Unlock) expires.
func (am *Manager) Unlock(addr []byte, keyAuth string) error { func (am *Manager) Unlock(addr common.Address, keyAuth string) error {
key, err := am.keyStore.GetKey(addr, keyAuth) key, err := am.keyStore.GetKey(addr, keyAuth)
if err != nil { if err != nil {
return err return err
@ -157,10 +157,10 @@ func (am *Manager) Accounts() ([]Account, error) {
return accounts, err return accounts, err
} }
func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { func (am *Manager) addUnlocked(addr common.Address, key *crypto.Key) *unlocked {
u := &unlocked{Key: key, abort: make(chan struct{})} u := &unlocked{Key: key, abort: make(chan struct{})}
am.mutex.Lock() am.mutex.Lock()
prev, found := am.unlocked[string(addr)] prev, found := am.unlocked[addr]
if found { if found {
// terminate dropLater for this key to avoid unexpected drops. // terminate dropLater for this key to avoid unexpected drops.
close(prev.abort) close(prev.abort)
@ -169,12 +169,12 @@ func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
// key, i.e. when Unlock was used. // key, i.e. when Unlock was used.
zeroKey(prev.PrivateKey) zeroKey(prev.PrivateKey)
} }
am.unlocked[string(addr)] = u am.unlocked[addr] = u
am.mutex.Unlock() am.mutex.Unlock()
return u return u
} }
func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) { func (am *Manager) dropLater(addr common.Address, u *unlocked, timeout time.Duration) {
t := time.NewTimer(timeout) t := time.NewTimer(timeout)
defer t.Stop() defer t.Stop()
select { select {
@ -186,9 +186,9 @@ func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) {
// was launched with. we can check that using pointer equality // was launched with. we can check that using pointer equality
// because the map stores a new pointer every time the key is // because the map stores a new pointer every time the key is
// unlocked. // unlocked.
if am.unlocked[string(addr)] == u { if am.unlocked[addr] == u {
zeroKey(u.PrivateKey) zeroKey(u.PrivateKey)
delete(am.unlocked, string(addr)) delete(am.unlocked, addr)
} }
am.mutex.Unlock() am.mutex.Unlock()
} }
@ -204,7 +204,7 @@ func zeroKey(k *ecdsa.PrivateKey) {
// USE WITH CAUTION = this will save an unencrypted private key on disk // USE WITH CAUTION = this will save an unencrypted private key on disk
// no cli or js interface // no cli or js interface
func (am *Manager) Export(path string, addr []byte, keyAuth string) error { func (am *Manager) Export(path string, addr common.Address, keyAuth string) error {
key, err := am.keyStore.GetKey(addr, keyAuth) key, err := am.keyStore.GetKey(addr, keyAuth)
if err != nil { if err != nil {
return err return err

View File

@ -126,7 +126,7 @@ func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
// Add the accouns to a new set // Add the accouns to a new set
accountSet := set.New() accountSet := set.New()
for _, account := range accounts { for _, account := range accounts {
accountSet.Add(common.BytesToAddress(account.Address)) accountSet.Add(account.Address)
} }
//ltxs := make([]*tx, len(txs)) //ltxs := make([]*tx, len(txs))
@ -275,14 +275,22 @@ func (js *jsre) verbosity(call otto.FunctionCall) otto.Value {
} }
func (js *jsre) startMining(call otto.FunctionCall) otto.Value { func (js *jsre) startMining(call otto.FunctionCall) otto.Value {
_, err := call.Argument(0).ToInteger() var (
if err != nil { threads int64
fmt.Println(err) err error
return otto.FalseValue() )
}
// threads now ignored
err = js.ethereum.StartMining() if len(call.ArgumentList) > 0 {
threads, err = call.Argument(0).ToInteger()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
} else {
threads = 4
}
err = js.ethereum.StartMining(int(threads))
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return otto.FalseValue() return otto.FalseValue()
@ -383,7 +391,7 @@ func (js *jsre) unlock(call otto.FunctionCall) otto.Value {
} }
} }
am := js.ethereum.AccountManager() am := js.ethereum.AccountManager()
err = am.TimedUnlock(common.FromHex(addr), passphrase, time.Duration(seconds)*time.Second) err = am.TimedUnlock(common.HexToAddress(addr), passphrase, time.Duration(seconds)*time.Second)
if err != nil { if err != nil {
fmt.Printf("Unlock account failed '%v'\n", err) fmt.Printf("Unlock account failed '%v'\n", err)
return otto.FalseValue() return otto.FalseValue()
@ -425,7 +433,7 @@ func (js *jsre) newAccount(call otto.FunctionCall) otto.Value {
fmt.Printf("Could not create the account: %v", err) fmt.Printf("Could not create the account: %v", err)
return otto.UndefinedValue() return otto.UndefinedValue()
} }
return js.re.ToVal(common.ToHex(acct.Address)) return js.re.ToVal(acct.Address.Hex())
} }
func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value { func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value {

View File

@ -1 +1 @@
{"code":"605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056","info":{"abiDefinition":[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}],"compilerVersion":"0.9.13","developerDoc":{"methods":{}},"language":"Solidity","languageVersion":"0","source":"contract test {\n /// @notice Will multiply `a` by 7.\n function multiply(uint a) returns(uint d) {\n return a * 7;\n }\n}\n","userDoc":{"methods":{"multiply(uint256)":{"notice":"Will multiply `a` by 7."}}}}} {"code":"605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056","info":{"abiDefinition":[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}],"compilerVersion":"0.9.17","developerDoc":{"methods":{}},"language":"Solidity","languageVersion":"0","source":"contract test {\n /// @notice Will multiply `a` by 7.\n function multiply(uint a) returns(uint d) {\n return a * 7;\n }\n}\n","userDoc":{"methods":{"multiply(uint256)":{"notice":"Will multiply `a` by 7."}}}}}

View File

@ -22,10 +22,11 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"path" "path/filepath"
"strings" "strings"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/docserver" "github.com/ethereum/go-ethereum/common/docserver"
"github.com/ethereum/go-ethereum/common/natspec" "github.com/ethereum/go-ethereum/common/natspec"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
@ -164,7 +165,7 @@ func (self *jsre) UnlockAccount(addr []byte) bool {
return false return false
} }
// TODO: allow retry // TODO: allow retry
if err := self.ethereum.AccountManager().Unlock(addr, pass); err != nil { if err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil {
return false return false
} else { } else {
fmt.Println("Account is now unlocked for this session.") fmt.Println("Account is now unlocked for this session.")
@ -209,7 +210,7 @@ func (self *jsre) interactive() {
} }
func (self *jsre) withHistory(op func(*os.File)) { func (self *jsre) withHistory(op func(*os.File)) {
hist, err := os.OpenFile(path.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm) hist, err := os.OpenFile(filepath.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil { if err != nil {
fmt.Printf("unable to open history file: %v\n", err) fmt.Printf("unable to open history file: %v\n", err)
return return

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
@ -25,10 +24,13 @@ import (
const ( const (
testSolcPath = "" testSolcPath = ""
solcVersion = "0.9.17"
testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674" testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674"
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
testBalance = "10000000000000000000" testBalance = "10000000000000000000"
// of empty string
testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
) )
var ( var (
@ -43,7 +45,7 @@ type testjethre struct {
} }
func (self *testjethre) UnlockAccount(acc []byte) bool { func (self *testjethre) UnlockAccount(acc []byte) bool {
err := self.ethereum.AccountManager().Unlock(acc, "") err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
if err != nil { if err != nil {
panic("unable to unlock") panic("unable to unlock")
} }
@ -66,7 +68,7 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
// set up mock genesis with balance on the testAddress // set up mock genesis with balance on the testAddress
core.GenesisData = []byte(testGenesis) core.GenesisData = []byte(testGenesis)
ks := crypto.NewKeyStorePassphrase(filepath.Join(tmp, "keys")) ks := crypto.NewKeyStorePassphrase(filepath.Join(tmp, "keystore"))
am := accounts.NewManager(ks) am := accounts.NewManager(ks)
ethereum, err := eth.New(&eth.Config{ ethereum, err := eth.New(&eth.Config{
DataDir: tmp, DataDir: tmp,
@ -93,7 +95,7 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
t.Fatal(err) t.Fatal(err)
} }
assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
ds, err := docserver.New("/") ds, err := docserver.New("/")
if err != nil { if err != nil {
t.Errorf("Error creating DocServer: %v", err) t.Errorf("Error creating DocServer: %v", err)
@ -215,7 +217,34 @@ func TestCheckTestAccountBalance(t *testing.T) {
checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"`+testBalance+`"`) checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"`+testBalance+`"`)
} }
func TestSignature(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
defer os.RemoveAll(tmp)
val, err := repl.re.Run(`eth.sign({from: "` + testAddress + `", data: "` + testHash + `"})`)
// This is a very preliminary test, lacking actual signature verification
if err != nil {
t.Errorf("Error runnig js: %v", err)
return
}
output := val.String()
t.Logf("Output: %v", output)
regex := regexp.MustCompile(`^0x[0-9a-f]{130}$`)
if !regex.MatchString(output) {
t.Errorf("Signature is not 65 bytes represented in hexadecimal.")
return
}
}
func TestContract(t *testing.T) { func TestContract(t *testing.T) {
t.Skip()
tmp, repl, ethereum := testJEthRE(t) tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil { if err := ethereum.Start(); err != nil {
@ -245,9 +274,16 @@ func TestContract(t *testing.T) {
checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"`+testAddress+`"`) checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"`+testAddress+`"`)
checkEvalJSON(t, repl, `source = "`+source+`"`, `"`+source+`"`) checkEvalJSON(t, repl, `source = "`+source+`"`, `"`+source+`"`)
_, err = compiler.New("") // if solc is found with right version, test it, otherwise read from file
sol, err := compiler.New("")
if err != nil { if err != nil {
t.Logf("solc not found: skipping compiler test") t.Logf("solc not found: skipping compiler test")
} else if sol.Version() != solcVersion {
err = fmt.Errorf("solc wrong version found (%v, expect %v): skipping compiler test", sol.Version(), solcVersion)
t.Log(err)
}
if err != nil {
info, err := ioutil.ReadFile("info_test.json") info, err := ioutil.ReadFile("info_test.json")
if err != nil { if err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
@ -259,6 +295,7 @@ func TestContract(t *testing.T) {
} else { } else {
checkEvalJSON(t, repl, `contract = eth.compile.solidity(source)`, string(contractInfo)) checkEvalJSON(t, repl, `contract = eth.compile.solidity(source)`, string(contractInfo))
} }
checkEvalJSON(t, repl, `contract.code`, `"605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056"`) checkEvalJSON(t, repl, `contract.code`, `"605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056"`)
checkEvalJSON( checkEvalJSON(
@ -298,7 +335,7 @@ multiply7 = new Multiply7(contractaddress);
} }
checkEvalJSON(t, repl, `filename = "/tmp/info.json"`, `"/tmp/info.json"`) checkEvalJSON(t, repl, `filename = "/tmp/info.json"`, `"/tmp/info.json"`)
checkEvalJSON(t, repl, `contenthash = admin.contractInfo.register(primary, contractaddress, contract, filename)`, `"0x57e577316ccee6514797d9de9823af2004fdfe22bcfb6e39bbb8f92f57dcc421"`) checkEvalJSON(t, repl, `contenthash = admin.contractInfo.register(primary, contractaddress, contract, filename)`, `"0x0d067e2dd99a4d8f0c0279738b17130dd415a89f24a23f0e7cf68c546ae3089d"`)
checkEvalJSON(t, repl, `admin.contractInfo.registerUrl(primary, contenthash, "file://"+filename)`, `true`) checkEvalJSON(t, repl, `admin.contractInfo.registerUrl(primary, contenthash, "file://"+filename)`, `true`)
if err != nil { if err != nil {
t.Errorf("unexpected error registering, got %v", err) t.Errorf("unexpected error registering, got %v", err)
@ -324,7 +361,7 @@ func checkEvalJSON(t *testing.T, re *testjethre, expr, want string) error {
} }
if err != nil { if err != nil {
_, file, line, _ := runtime.Caller(1) _, file, line, _ := runtime.Caller(1)
file = path.Base(file) file = filepath.Base(file)
fmt.Printf("\t%s:%d: %v\n", file, line, err) fmt.Printf("\t%s:%d: %v\n", file, line, err)
t.Fail() t.Fail()
} }

View File

@ -26,7 +26,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
@ -51,7 +50,7 @@ import _ "net/http/pprof"
const ( const (
ClientIdentifier = "Geth" ClientIdentifier = "Geth"
Version = "0.9.19" Version = "0.9.20"
) )
var ( var (
@ -366,11 +365,10 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass
// Load startup keys. XXX we are going to need a different format // Load startup keys. XXX we are going to need a different format
// Attempt to unlock the account // Attempt to unlock the account
passphrase = getPassPhrase(ctx, "", false) passphrase = getPassPhrase(ctx, "", false)
accbytes := common.FromHex(account) if len(account) == 0 {
if len(accbytes) == 0 {
utils.Fatalf("Invalid account address '%s'", account) utils.Fatalf("Invalid account address '%s'", account)
} }
err = am.Unlock(accbytes, passphrase) err = am.Unlock(common.StringToAddress(account), passphrase)
if err != nil { if err != nil {
utils.Fatalf("Unlock account failed '%v'", err) utils.Fatalf("Unlock account failed '%v'", err)
} }
@ -386,11 +384,11 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
account := ctx.GlobalString(utils.UnlockedAccountFlag.Name) account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
if len(account) > 0 { if len(account) > 0 {
if account == "primary" { if account == "primary" {
accbytes, err := am.Primary() primaryAcc, err := am.Primary()
if err != nil { if err != nil {
utils.Fatalf("no primary account: %v", err) utils.Fatalf("no primary account: %v", err)
} }
account = common.ToHex(accbytes) account = primaryAcc.Hex()
} }
unlockAccount(ctx, am, account) unlockAccount(ctx, am, account)
} }
@ -401,7 +399,7 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
} }
} }
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
if err := eth.StartMining(); err != nil { if err := eth.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name)); err != nil {
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
} }
} }
@ -565,7 +563,7 @@ func upgradeDb(ctx *cli.Context) {
} }
filename := fmt.Sprintf("blockchain_%d_%s.chain", bcVersion, time.Now().Format("2006-01-02_15:04:05")) filename := fmt.Sprintf("blockchain_%d_%s.chain", bcVersion, time.Now().Format("2006-01-02_15:04:05"))
exportFile := path.Join(ctx.GlobalString(utils.DataDirFlag.Name), filename) exportFile := filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), filename)
err = utils.ExportChain(ethereum.ChainManager(), exportFile) err = utils.ExportChain(ethereum.ChainManager(), exportFile)
if err != nil { if err != nil {
@ -576,7 +574,7 @@ func upgradeDb(ctx *cli.Context) {
ethereum.StateDb().Close() ethereum.StateDb().Close()
ethereum.ExtraDb().Close() ethereum.ExtraDb().Close()
os.RemoveAll(path.Join(ctx.GlobalString(utils.DataDirFlag.Name), "blockchain")) os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "blockchain"))
ethereum, err = eth.New(cfg) ethereum, err = eth.New(cfg)
if err != nil { if err != nil {

View File

@ -27,7 +27,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"path" "path/filepath"
"runtime" "runtime"
"sort" "sort"
"time" "time"
@ -79,7 +79,7 @@ type Gui struct {
// Create GUI, but doesn't start it // Create GUI, but doesn't start it
func NewWindow(ethereum *eth.Ethereum) *Gui { func NewWindow(ethereum *eth.Ethereum) *Gui {
db, err := ethdb.NewLDBDatabase(path.Join(ethereum.DataDir, "tx_database")) db, err := ethdb.NewLDBDatabase(filepath.Join(ethereum.DataDir, "tx_database"))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -92,7 +92,7 @@ func NewWindow(ethereum *eth.Ethereum) *Gui {
plugins: make(map[string]plugin), plugins: make(map[string]plugin),
serviceEvents: make(chan ServEv, 1), serviceEvents: make(chan ServEv, 1),
} }
data, _ := ioutil.ReadFile(path.Join(ethereum.DataDir, "plugins.json")) data, _ := ioutil.ReadFile(filepath.Join(ethereum.DataDir, "plugins.json"))
json.Unmarshal(data, &gui.plugins) json.Unmarshal(data, &gui.plugins)
return gui return gui
@ -232,7 +232,7 @@ func (self *Gui) loadMergedMiningOptions() {
func (gui *Gui) insertTransaction(window string, tx *types.Transaction) { func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
var inout string var inout string
from, _ := tx.From() from, _ := tx.From()
if gui.eth.AccountManager().HasAccount(common.Hex2Bytes(from.Hex())) { if gui.eth.AccountManager().HasAccount(from) {
inout = "send" inout = "send"
} else { } else {
inout = "recv" inout = "recv"

View File

@ -26,7 +26,6 @@ import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"os" "os"
"path"
"path/filepath" "path/filepath"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -80,7 +79,7 @@ func (app *HtmlApplication) RootFolder() string {
if err != nil { if err != nil {
return "" return ""
} }
return path.Dir(common.WindonizePath(folder.RequestURI())) return filepath.Dir(common.WindonizePath(folder.RequestURI()))
} }
func (app *HtmlApplication) RecursiveFolders() []os.FileInfo { func (app *HtmlApplication) RecursiveFolders() []os.FileInfo {
files, _ := ioutil.ReadDir(app.RootFolder()) files, _ := ioutil.ReadDir(app.RootFolder())

View File

@ -22,7 +22,7 @@ package main
import ( import (
"io/ioutil" "io/ioutil"
"path" "path/filepath"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -110,7 +110,7 @@ func (ui *UiLib) ConnectToPeer(nodeURL string) {
} }
func (ui *UiLib) AssetPath(p string) string { func (ui *UiLib) AssetPath(p string) string {
return path.Join(ui.assetPath, p) return filepath.Join(ui.assetPath, p)
} }
func (self *UiLib) Transact(params map[string]interface{}) (string, error) { func (self *UiLib) Transact(params map[string]interface{}) (string, error) {
@ -127,7 +127,7 @@ func (self *UiLib) Transact(params map[string]interface{}) (string, error) {
) )
} }
func (self *UiLib) Call(params map[string]interface{}) (string, error) { func (self *UiLib) Call(params map[string]interface{}) (string, string, error) {
object := mapToTxParams(params) object := mapToTxParams(params)
return self.XEth.Call( return self.XEth.Call(
@ -159,7 +159,7 @@ func (self *UiLib) RemoveLocalTransaction(id int) {
func (self *UiLib) ToggleMining() bool { func (self *UiLib) ToggleMining() bool {
if !self.eth.IsMining() { if !self.eth.IsMining() {
err := self.eth.StartMining() err := self.eth.StartMining(4)
return err == nil return err == nil
} else { } else {
self.eth.StopMining() self.eth.StopMining()
@ -218,7 +218,7 @@ func (self *UiLib) Messages(id int) *common.List {
} }
func (self *UiLib) ReadFile(p string) string { func (self *UiLib) ReadFile(p string) string {
content, err := ioutil.ReadFile(self.AssetPath(path.Join("ext", p))) content, err := ioutil.ReadFile(self.AssetPath(filepath.Join("ext", p)))
if err != nil { if err != nil {
guilogger.Infoln("error reading file", p, ":", err) guilogger.Infoln("error reading file", p, ":", err)
} }

View File

@ -7,7 +7,7 @@ import (
"math/big" "math/big"
"net/http" "net/http"
"os" "os"
"path" "path/filepath"
"runtime" "runtime"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
@ -55,7 +55,7 @@ OPTIONS:
// NewApp creates an app with sane defaults. // NewApp creates an app with sane defaults.
func NewApp(version, usage string) *cli.App { func NewApp(version, usage string) *cli.App {
app := cli.NewApp() app := cli.NewApp()
app.Name = path.Base(os.Args[0]) app.Name = filepath.Base(os.Args[0])
app.Author = "" app.Author = ""
//app.Authors = nil //app.Authors = nil
app.Email = "" app.Email = ""
@ -319,17 +319,17 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Database) { func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Database) {
dataDir := ctx.GlobalString(DataDirFlag.Name) dataDir := ctx.GlobalString(DataDirFlag.Name)
blockDb, err := ethdb.NewLDBDatabase(path.Join(dataDir, "blockchain")) blockDb, err := ethdb.NewLDBDatabase(filepath.Join(dataDir, "blockchain"))
if err != nil { if err != nil {
Fatalf("Could not open database: %v", err) Fatalf("Could not open database: %v", err)
} }
stateDb, err := ethdb.NewLDBDatabase(path.Join(dataDir, "state")) stateDb, err := ethdb.NewLDBDatabase(filepath.Join(dataDir, "state"))
if err != nil { if err != nil {
Fatalf("Could not open database: %v", err) Fatalf("Could not open database: %v", err)
} }
extraDb, err := ethdb.NewLDBDatabase(path.Join(dataDir, "extra")) extraDb, err := ethdb.NewLDBDatabase(filepath.Join(dataDir, "extra"))
if err != nil { if err != nil {
Fatalf("Could not open database: %v", err) Fatalf("Could not open database: %v", err)
} }
@ -346,7 +346,7 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Dat
func GetAccountManager(ctx *cli.Context) *accounts.Manager { func GetAccountManager(ctx *cli.Context) *accounts.Manager {
dataDir := ctx.GlobalString(DataDirFlag.Name) dataDir := ctx.GlobalString(DataDirFlag.Name)
ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) ks := crypto.NewKeyStorePassphrase(filepath.Join(dataDir, "keystore"))
return accounts.NewManager(ks) return accounts.NewManager(ks)
} }

View File

@ -7,7 +7,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
@ -88,6 +87,10 @@ func (sol *Solidity) Info() string {
return fmt.Sprintf("solc v%s\nSolidity Compiler: %s\n%s", sol.version, sol.solcPath, flair) return fmt.Sprintf("solc v%s\nSolidity Compiler: %s\n%s", sol.version, sol.solcPath, flair)
} }
func (sol *Solidity) Version() string {
return sol.version
}
func (sol *Solidity) Compile(source string) (contract *Contract, err error) { func (sol *Solidity) Compile(source string) (contract *Contract, err error) {
if len(source) == 0 { if len(source) == 0 {
@ -126,10 +129,10 @@ func (sol *Solidity) Compile(source string) (contract *Contract, err error) {
_, file := filepath.Split(matches[0]) _, file := filepath.Split(matches[0])
base := strings.Split(file, ".")[0] base := strings.Split(file, ".")[0]
codeFile := path.Join(wd, base+".binary") codeFile := filepath.Join(wd, base+".binary")
abiDefinitionFile := path.Join(wd, base+".abi") abiDefinitionFile := filepath.Join(wd, base+".abi")
userDocFile := path.Join(wd, base+".docuser") userDocFile := filepath.Join(wd, base+".docuser")
developerDocFile := path.Join(wd, base+".docdev") developerDocFile := filepath.Join(wd, base+".docdev")
code, err := ioutil.ReadFile(codeFile) code, err := ioutil.ReadFile(codeFile)
if err != nil { if err != nil {

View File

@ -9,6 +9,8 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
const solcVersion = "0.9.17"
var ( var (
source = ` source = `
contract test { contract test {
@ -19,9 +21,9 @@ contract test {
} }
` `
code = "605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056" code = "605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056"
info = `{"source":"\ncontract test {\n /// @notice Will multiply ` + "`a`" + ` by 7.\n function multiply(uint a) returns(uint d) {\n return a * 7;\n }\n}\n","language":"Solidity","languageVersion":"0","compilerVersion":"0.9.13","abiDefinition":[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}],"userDoc":{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}},"developerDoc":{"methods":{}}}` info = `{"source":"\ncontract test {\n /// @notice Will multiply ` + "`a`" + ` by 7.\n function multiply(uint a) returns(uint d) {\n return a * 7;\n }\n}\n","language":"Solidity","languageVersion":"0","compilerVersion":"0.9.17","abiDefinition":[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}],"userDoc":{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}},"developerDoc":{"methods":{}}}`
infohash = common.HexToHash("0xfdb031637e8a1c1891143f8d129ebc7f7c4e4b41ecad8c85abe1756190f74204") infohash = common.HexToHash("0x834075768a68e500e459b9c3213750c84de3df47156500cb01bb664d3f88c60a")
) )
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
@ -34,14 +36,16 @@ func TestCompiler(t *testing.T) {
t.Errorf("error compiling source. result %v: %v", contract, err) t.Errorf("error compiling source. result %v: %v", contract, err)
return return
} }
if contract.Code != code { /*
t.Errorf("wrong code, expected\n%s, got\n%s", code, contract.Code) if contract.Code != code {
} t.Errorf("wrong code, expected\n%s, got\n%s", code, contract.Code)
}
*/
} }
func TestCompileError(t *testing.T) { func TestCompileError(t *testing.T) {
sol, err := New("") sol, err := New("")
if err != nil { if err != nil || sol.version != solcVersion {
t.Skip("no solc installed") t.Skip("no solc installed")
} }
contract, err := sol.Compile(source[2:]) contract, err := sol.Compile(source[2:])

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
@ -84,7 +85,7 @@ type testFrontend struct {
} }
func (self *testFrontend) UnlockAccount(acc []byte) bool { func (self *testFrontend) UnlockAccount(acc []byte) bool {
self.ethereum.AccountManager().Unlock(acc, "password") self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "password")
return true return true
} }
@ -103,19 +104,19 @@ func testEth(t *testing.T) (ethereum *eth.Ethereum, err error) {
os.RemoveAll("/tmp/eth-natspec/") os.RemoveAll("/tmp/eth-natspec/")
err = os.MkdirAll("/tmp/eth-natspec/keys", os.ModePerm) err = os.MkdirAll("/tmp/eth-natspec/keystore", os.ModePerm)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// create a testAddress // create a testAddress
ks := crypto.NewKeyStorePassphrase("/tmp/eth-natspec/keys") ks := crypto.NewKeyStorePassphrase("/tmp/eth-natspec/keystore")
am := accounts.NewManager(ks) am := accounts.NewManager(ks)
testAccount, err := am.NewAccount("password") testAccount, err := am.NewAccount("password")
if err != nil { if err != nil {
panic(err) panic(err)
} }
testAddress := common.Bytes2Hex(testAccount.Address) testAddress := strings.TrimPrefix(testAccount.Address.Hex(), "0x")
// set up mock genesis with balance on the testAddress // set up mock genesis with balance on the testAddress
core.GenesisData = []byte(`{ core.GenesisData = []byte(`{

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"os" "os"
"os/user" "os/user"
"path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
@ -44,22 +43,22 @@ func FileExist(filePath string) bool {
} }
func AbsolutePath(Datadir string, filename string) string { func AbsolutePath(Datadir string, filename string) string {
if path.IsAbs(filename) { if filepath.IsAbs(filename) {
return filename return filename
} }
return path.Join(Datadir, filename) return filepath.Join(Datadir, filename)
} }
func DefaultAssetPath() string { func DefaultAssetPath() string {
var assetPath string var assetPath string
pwd, _ := os.Getwd() pwd, _ := os.Getwd()
srcdir := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist") srcdir := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist")
// If the current working directory is the go-ethereum dir // If the current working directory is the go-ethereum dir
// assume a debug build and use the source directory as // assume a debug build and use the source directory as
// asset directory. // asset directory.
if pwd == srcdir { if pwd == srcdir {
assetPath = path.Join(pwd, "assets") assetPath = filepath.Join(pwd, "assets")
} else { } else {
switch runtime.GOOS { switch runtime.GOOS {
case "darwin": case "darwin":
@ -67,9 +66,9 @@ func DefaultAssetPath() string {
exedir, _ := osext.ExecutableFolder() exedir, _ := osext.ExecutableFolder()
assetPath = filepath.Join(exedir, "..", "Resources") assetPath = filepath.Join(exedir, "..", "Resources")
case "linux": case "linux":
assetPath = path.Join("usr", "share", "mist") assetPath = filepath.Join("usr", "share", "mist")
case "windows": case "windows":
assetPath = path.Join(".", "assets") assetPath = filepath.Join(".", "assets")
default: default:
assetPath = "." assetPath = "."
} }
@ -78,7 +77,7 @@ func DefaultAssetPath() string {
// Check if the assetPath exists. If not, try the source directory // Check if the assetPath exists. If not, try the source directory
// This happens when binary is run from outside cmd/mist directory // This happens when binary is run from outside cmd/mist directory
if _, err := os.Stat(assetPath); os.IsNotExist(err) { if _, err := os.Stat(assetPath); os.IsNotExist(err) {
assetPath = path.Join(srcdir, "assets") assetPath = filepath.Join(srcdir, "assets")
} }
return assetPath return assetPath
@ -87,11 +86,11 @@ func DefaultAssetPath() string {
func DefaultDataDir() string { func DefaultDataDir() string {
usr, _ := user.Current() usr, _ := user.Current()
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
return path.Join(usr.HomeDir, "Library", "Ethereum") return filepath.Join(usr.HomeDir, "Library", "Ethereum")
} else if runtime.GOOS == "windows" { } else if runtime.GOOS == "windows" {
return path.Join(usr.HomeDir, "AppData", "Roaming", "Ethereum") return filepath.Join(usr.HomeDir, "AppData", "Roaming", "Ethereum")
} else { } else {
return path.Join(usr.HomeDir, ".ethereum") return filepath.Join(usr.HomeDir, ".ethereum")
} }
} }

View File

@ -44,12 +44,6 @@ func CurrencyToString(num *big.Int) string {
) )
switch { switch {
case num.Cmp(Douglas) >= 0:
fin = new(big.Int).Div(num, Douglas)
denom = "Douglas"
case num.Cmp(Einstein) >= 0:
fin = new(big.Int).Div(num, Einstein)
denom = "Einstein"
case num.Cmp(Ether) >= 0: case num.Cmp(Ether) >= 0:
fin = new(big.Int).Div(num, Ether) fin = new(big.Int).Div(num, Ether)
denom = "Ether" denom = "Ether"

View File

@ -25,8 +25,6 @@ func (s *SizeSuite) TestStorageSizeString(c *checker.C) {
} }
func (s *CommonSuite) TestCommon(c *checker.C) { func (s *CommonSuite) TestCommon(c *checker.C) {
douglas := CurrencyToString(BigPow(10, 43))
einstein := CurrencyToString(BigPow(10, 22))
ether := CurrencyToString(BigPow(10, 19)) ether := CurrencyToString(BigPow(10, 19))
finney := CurrencyToString(BigPow(10, 16)) finney := CurrencyToString(BigPow(10, 16))
szabo := CurrencyToString(BigPow(10, 13)) szabo := CurrencyToString(BigPow(10, 13))
@ -35,8 +33,6 @@ func (s *CommonSuite) TestCommon(c *checker.C) {
ada := CurrencyToString(BigPow(10, 4)) ada := CurrencyToString(BigPow(10, 4))
wei := CurrencyToString(big.NewInt(10)) wei := CurrencyToString(big.NewInt(10))
c.Assert(douglas, checker.Equals, "10 Douglas")
c.Assert(einstein, checker.Equals, "10 Einstein")
c.Assert(ether, checker.Equals, "10 Ether") c.Assert(ether, checker.Equals, "10 Ether")
c.Assert(finney, checker.Equals, "10 Finney") c.Assert(finney, checker.Equals, "10 Finney")
c.Assert(szabo, checker.Equals, "10 Szabo") c.Assert(szabo, checker.Equals, "10 Szabo")
@ -45,13 +41,3 @@ func (s *CommonSuite) TestCommon(c *checker.C) {
c.Assert(ada, checker.Equals, "10 Ada") c.Assert(ada, checker.Equals, "10 Ada")
c.Assert(wei, checker.Equals, "10 Wei") c.Assert(wei, checker.Equals, "10 Wei")
} }
func (s *CommonSuite) TestLarge(c *checker.C) {
douglaslarge := CurrencyToString(BigPow(100000000, 43))
adalarge := CurrencyToString(BigPow(100000000, 4))
weilarge := CurrencyToString(big.NewInt(100000000))
c.Assert(douglaslarge, checker.Equals, "10000E298 Douglas")
c.Assert(adalarge, checker.Equals, "10000E7 Einstein")
c.Assert(weilarge, checker.Equals, "100 Babbage")
}

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"path" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"testing" "testing"
@ -94,7 +94,7 @@ func testChain(chainB types.Blocks, bman *BlockProcessor) (*big.Int, error) {
} }
func loadChain(fn string, t *testing.T) (types.Blocks, error) { func loadChain(fn string, t *testing.T) (types.Blocks, error) {
fh, err := os.OpenFile(path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "_data", fn), os.O_RDONLY, os.ModePerm) fh, err := os.OpenFile(filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "_data", fn), os.O_RDONLY, os.ModePerm)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,8 +1,10 @@
package core package core
import ( import (
"github.com/ethereum/go-ethereum/core/types" "math/big"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
) )
// TxPreEvent is posted when a transaction enters the transaction pool. // TxPreEvent is posted when a transaction enters the transaction pool.
@ -44,6 +46,8 @@ type ChainUncleEvent struct {
type ChainHeadEvent struct{ Block *types.Block } type ChainHeadEvent struct{ Block *types.Block }
type GasPriceChanged struct{ Price *big.Int }
// Mining operation events // Mining operation events
type StartMining struct{} type StartMining struct{}
type TopMining struct{} type TopMining struct{}

View File

@ -1,12 +1,14 @@
package core package core
import ( import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
) )
type Backend interface { type Backend interface {
AccountManager() *accounts.Manager
BlockProcessor() *BlockProcessor BlockProcessor() *BlockProcessor
ChainManager() *ChainManager ChainManager() *ChainManager
TxPool() *TxPool TxPool() *TxPool

View File

@ -21,7 +21,7 @@ var (
ErrInvalidSender = errors.New("Invalid sender") ErrInvalidSender = errors.New("Invalid sender")
ErrNonce = errors.New("Nonce too low") ErrNonce = errors.New("Nonce too low")
ErrBalance = errors.New("Insufficient balance") ErrBalance = errors.New("Insufficient balance")
ErrNonExistentAccount = errors.New("Account does not exist") ErrNonExistentAccount = errors.New("Account does not exist or account balance too low")
ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value") ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value")
ErrIntrinsicGas = errors.New("Intrinsic gas too low") ErrIntrinsicGas = errors.New("Intrinsic gas too low")
ErrGasLimit = errors.New("Exceeds block gas limit") ErrGasLimit = errors.New("Exceeds block gas limit")

View File

@ -181,11 +181,11 @@ func Decrypt(prv *ecdsa.PrivateKey, ct []byte) ([]byte, error) {
// Used only by block tests. // Used only by block tests.
func ImportBlockTestKey(privKeyBytes []byte) error { func ImportBlockTestKey(privKeyBytes []byte) error {
ks := NewKeyStorePassphrase(common.DefaultDataDir() + "/keys") ks := NewKeyStorePassphrase(common.DefaultDataDir() + "/keystore")
ecKey := ToECDSA(privKeyBytes) ecKey := ToECDSA(privKeyBytes)
key := &Key{ key := &Key{
Id: uuid.NewRandom(), Id: uuid.NewRandom(),
Address: PubkeyToAddress(ecKey.PublicKey), Address: common.BytesToAddress(PubkeyToAddress(ecKey.PublicKey)),
PrivateKey: ecKey, PrivateKey: ecKey,
} }
err := ks.StoreKey(key, "") err := ks.StoreKey(key, "")
@ -231,13 +231,13 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
ecKey := ToECDSA(ethPriv) ecKey := ToECDSA(ethPriv)
key = &Key{ key = &Key{
Id: nil, Id: nil,
Address: PubkeyToAddress(ecKey.PublicKey), Address: common.BytesToAddress(PubkeyToAddress(ecKey.PublicKey)),
PrivateKey: ecKey, PrivateKey: ecKey,
} }
derivedAddr := common.Bytes2Hex(key.Address) derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
expectedAddr := preSaleKeyStruct.EthAddr expectedAddr := preSaleKeyStruct.EthAddr
if derivedAddr != expectedAddr { if derivedAddr != expectedAddr {
err = errors.New("decrypted addr not equal to expected addr") err = errors.New(fmt.Sprintf("decrypted addr not equal to expected addr ", derivedAddr, expectedAddr))
} }
return key, err return key, err
} }
@ -252,7 +252,7 @@ func aesCBCDecrypt(key []byte, cipherText []byte, iv []byte) (plainText []byte,
decrypter.CryptBlocks(paddedPlainText, cipherText) decrypter.CryptBlocks(paddedPlainText, cipherText)
plainText = PKCS7Unpad(paddedPlainText) plainText = PKCS7Unpad(paddedPlainText)
if plainText == nil { if plainText == nil {
err = errors.New("Decryption failed: PKCS7Unpad failed after decryption") err = errors.New("Decryption failed: PKCS7Unpad failed after AES decryption")
} }
return plainText, err return plainText, err
} }

View File

@ -26,44 +26,69 @@ package crypto
import ( import (
"bytes" "bytes"
"crypto/ecdsa" "crypto/ecdsa"
"encoding/hex"
"encoding/json" "encoding/json"
"io" "io"
"code.google.com/p/go-uuid/uuid" "code.google.com/p/go-uuid/uuid"
"github.com/ethereum/go-ethereum/common"
)
const (
version = "1"
) )
type Key struct { type Key struct {
Id uuid.UUID // Version 4 "random" for unique id not derived from key data Id uuid.UUID // Version 4 "random" for unique id not derived from key data
// to simplify lookups we also store the address // to simplify lookups we also store the address
Address []byte Address common.Address
// we only store privkey as pubkey/address can be derived from it // we only store privkey as pubkey/address can be derived from it
// privkey in this struct is always in plaintext // privkey in this struct is always in plaintext
PrivateKey *ecdsa.PrivateKey PrivateKey *ecdsa.PrivateKey
} }
type plainKeyJSON struct { type plainKeyJSON struct {
Id []byte Address string `json:"address"`
Address []byte PrivateKey string `json:"privatekey"`
PrivateKey []byte Id string `json:"id"`
} Version string `json:"version"`
type cipherJSON struct {
Salt []byte
IV []byte
CipherText []byte
} }
type encryptedKeyJSON struct { type encryptedKeyJSON struct {
Id []byte Address string `json:"address"`
Address []byte Crypto cryptoJSON
Crypto cipherJSON Id string `json:"id"`
Version string `json:"version"`
}
type cryptoJSON struct {
Cipher string `json:"cipher"`
CipherText string `json:"ciphertext"`
CipherParams cipherparamsJSON `json:"cipherparams"`
KDF string `json:"kdf"`
KDFParams scryptParamsJSON `json:"kdfparams"`
MAC string `json:"mac"`
Version string `json:"version"`
}
type cipherparamsJSON struct {
IV string `json:"iv"`
}
type scryptParamsJSON struct {
N int `json:"n"`
R int `json:"r"`
P int `json:"p"`
DkLen int `json:"dklen"`
Salt string `json:"salt"`
} }
func (k *Key) MarshalJSON() (j []byte, err error) { func (k *Key) MarshalJSON() (j []byte, err error) {
jStruct := plainKeyJSON{ jStruct := plainKeyJSON{
k.Id, hex.EncodeToString(k.Address[:]),
k.Address, hex.EncodeToString(FromECDSA(k.PrivateKey)),
FromECDSA(k.PrivateKey), k.Id.String(),
version,
} }
j, err = json.Marshal(jStruct) j, err = json.Marshal(jStruct)
return j, err return j, err
@ -77,19 +102,29 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) {
} }
u := new(uuid.UUID) u := new(uuid.UUID)
*u = keyJSON.Id *u = uuid.Parse(keyJSON.Id)
k.Id = *u k.Id = *u
k.Address = keyJSON.Address addr, err := hex.DecodeString(keyJSON.Address)
k.PrivateKey = ToECDSA(keyJSON.PrivateKey) if err != nil {
return err
}
return err privkey, err := hex.DecodeString(keyJSON.PrivateKey)
if err != nil {
return err
}
k.Address = common.BytesToAddress(addr)
k.PrivateKey = ToECDSA(privkey)
return nil
} }
func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
id := uuid.NewRandom() id := uuid.NewRandom()
key := &Key{ key := &Key{
Id: id, Id: id,
Address: PubkeyToAddress(privateKeyECDSA.PublicKey), Address: common.BytesToAddress(PubkeyToAddress(privateKeyECDSA.PublicKey)),
PrivateKey: privateKeyECDSA, PrivateKey: privateKeyECDSA,
} }
return key return key

View File

@ -28,24 +28,25 @@ the private key is encrypted and on disk uses another JSON encoding.
Cryptography: Cryptography:
1. Encryption key is scrypt derived key from user passphrase. Scrypt parameters 1. Encryption key is first 16 bytes of SHA3-256 of first 16 bytes of
scrypt derived key from user passphrase. Scrypt parameters
(work factors) [1][2] are defined as constants below. (work factors) [1][2] are defined as constants below.
2. Scrypt salt is 32 random bytes from CSPRNG. It is appended to ciphertext. 2. Scrypt salt is 32 random bytes from CSPRNG.
3. Checksum is SHA3 of the private key bytes. It's stored in plain next to ciphertext in key file.
4. Plaintext is concatenation of private key bytes and checksum. 3. MAC is SHA3-256 of concatenation of ciphertext and last 16 bytes of scrypt derived key.
5. Encryption algo is AES 256 CBC [3][4] 4. Plaintext is the EC private key bytes.
6. CBC IV is 16 random bytes from CSPRNG. It is appended to ciphertext. 5. Encryption algo is AES 128 CBC [3][4]
6. CBC IV is 16 random bytes from CSPRNG.
It's stored in plain next to ciphertext in key file.
7. Plaintext padding is PKCS #7 [5][6] 7. Plaintext padding is PKCS #7 [5][6]
Encoding: Encoding:
1. On disk, ciphertext, salt and IV are encoded in a nested JSON object. 1. On disk, the ciphertext, MAC, salt and IV are encoded in a nested JSON object.
cat a key file to see the structure. cat a key file to see the structure.
2. byte arrays are base64 JSON strings. 2. byte arrays are base64 JSON strings.
3. The EC private key bytes are in uncompressed form [7]. 3. The EC private key bytes are in uncompressed form [7].
They are a big-endian byte slice of the absolute value of D [8][9]. They are a big-endian byte slice of the absolute value of D [8][9].
4. The checksum is the last 32 bytes of the plaintext byte array and the
private key is the preceeding bytes.
References: References:
@ -72,14 +73,17 @@ import (
"errors" "errors"
"io" "io"
"os" "os"
"path" "path/filepath"
"code.google.com/p/go-uuid/uuid" "code.google.com/p/go-uuid/uuid"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/ethereum/go-ethereum/crypto/randentropy"
"golang.org/x/crypto/scrypt" "golang.org/x/crypto/scrypt"
) )
const ( const (
keyHeaderVersion = "1"
keyHeaderKDF = "scrypt"
// 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU. // 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
scryptN = 1 << 18 scryptN = 1 << 18
scryptr = 8 scryptr = 8
@ -99,7 +103,7 @@ func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *K
return GenerateNewKeyDefault(ks, rand, auth) return GenerateNewKeyDefault(ks, rand, auth)
} }
func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *Key, err error) { func (ks keyStorePassphrase) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
keyBytes, keyId, err := DecryptKey(ks, keyAddr, auth) keyBytes, keyId, err := DecryptKey(ks, keyAddr, auth)
if err != nil { if err != nil {
return nil, err return nil, err
@ -112,43 +116,63 @@ func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *Key, err
return key, err return key, err
} }
func (ks keyStorePassphrase) GetKeyAddresses() (addresses [][]byte, err error) { func (ks keyStorePassphrase) GetKeyAddresses() (addresses []common.Address, err error) {
return GetKeyAddresses(ks.keysDirPath) return GetKeyAddresses(ks.keysDirPath)
} }
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
authArray := []byte(auth) authArray := []byte(auth)
salt := randentropy.GetEntropyMixed(32) salt := randentropy.GetEntropyCSPRNG(32)
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
if err != nil { if err != nil {
return err return err
} }
keyBytes := FromECDSA(key.PrivateKey) encryptKey := Sha3(derivedKey[:16])[:16]
keyBytesHash := Sha3(keyBytes)
toEncrypt := PKCS7Pad(append(keyBytes, keyBytesHash...))
AES256Block, err := aes.NewCipher(derivedKey) keyBytes := FromECDSA(key.PrivateKey)
toEncrypt := PKCS7Pad(keyBytes)
AES128Block, err := aes.NewCipher(encryptKey)
if err != nil { if err != nil {
return err return err
} }
iv := randentropy.GetEntropyMixed(aes.BlockSize) // 16 iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv) AES128CBCEncrypter := cipher.NewCBCEncrypter(AES128Block, iv)
cipherText := make([]byte, len(toEncrypt)) cipherText := make([]byte, len(toEncrypt))
AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt) AES128CBCEncrypter.CryptBlocks(cipherText, toEncrypt)
cipherStruct := cipherJSON{ mac := Sha3(derivedKey[16:32], cipherText)
salt,
iv, scryptParamsJSON := scryptParamsJSON{
cipherText, N: scryptN,
R: scryptr,
P: scryptp,
DkLen: scryptdkLen,
Salt: hex.EncodeToString(salt),
} }
keyStruct := encryptedKeyJSON{
key.Id, cipherParamsJSON := cipherparamsJSON{
key.Address, IV: hex.EncodeToString(iv),
cipherStruct,
} }
keyJSON, err := json.Marshal(keyStruct)
cryptoStruct := cryptoJSON{
Cipher: "aes-128-cbc",
CipherText: hex.EncodeToString(cipherText),
CipherParams: cipherParamsJSON,
KDF: "scrypt",
KDFParams: scryptParamsJSON,
MAC: hex.EncodeToString(mac),
Version: "1",
}
encryptedKeyJSON := encryptedKeyJSON{
hex.EncodeToString(key.Address[:]),
cryptoStruct,
key.Id.String(),
version,
}
keyJSON, err := json.Marshal(encryptedKeyJSON)
if err != nil { if err != nil {
return err return err
} }
@ -156,18 +180,18 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON) return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON)
} }
func (ks keyStorePassphrase) DeleteKey(keyAddr []byte, auth string) (err error) { func (ks keyStorePassphrase) DeleteKey(keyAddr common.Address, auth string) (err error) {
// only delete if correct passphrase is given // only delete if correct passphrase is given
_, _, err = DecryptKey(ks, keyAddr, auth) _, _, err = DecryptKey(ks, keyAddr, auth)
if err != nil { if err != nil {
return err return err
} }
keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr)) keyDirPath := filepath.Join(ks.keysDirPath, hex.EncodeToString(keyAddr[:]))
return os.RemoveAll(keyDirPath) return os.RemoveAll(keyDirPath)
} }
func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []byte, keyId []byte, err error) { func DecryptKey(ks keyStorePassphrase, keyAddr common.Address, auth string) (keyBytes []byte, keyId []byte, err error) {
fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr) fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -176,25 +200,48 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []
keyProtected := new(encryptedKeyJSON) keyProtected := new(encryptedKeyJSON)
err = json.Unmarshal(fileContent, keyProtected) err = json.Unmarshal(fileContent, keyProtected)
keyId = keyProtected.Id keyId = uuid.Parse(keyProtected.Id)
salt := keyProtected.Crypto.Salt
iv := keyProtected.Crypto.IV mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
cipherText := keyProtected.Crypto.CipherText if err != nil {
return nil, nil, err
}
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
if err != nil {
return nil, nil, err
}
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
if err != nil {
return nil, nil, err
}
salt, err := hex.DecodeString(keyProtected.Crypto.KDFParams.Salt)
if err != nil {
return nil, nil, err
}
n := keyProtected.Crypto.KDFParams.N
r := keyProtected.Crypto.KDFParams.R
p := keyProtected.Crypto.KDFParams.P
dkLen := keyProtected.Crypto.KDFParams.DkLen
authArray := []byte(auth) authArray := []byte(auth)
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) derivedKey, err := scrypt.Key(authArray, salt, n, r, p, dkLen)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
calculatedMAC := Sha3(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
err = errors.New("Decryption failed: MAC mismatch")
return nil, nil, err
}
plainText, err := aesCBCDecrypt(Sha3(derivedKey[:16])[:16], cipherText, iv)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
keyBytes = plainText[:len(plainText)-32] return plainText, keyId, err
keyBytesHash := plainText[len(plainText)-32:]
if !bytes.Equal(Sha3(keyBytes), keyBytesHash) {
err = errors.New("Decryption failed: checksum mismatch")
return nil, nil, err
}
return keyBytes, keyId, err
} }

View File

@ -27,20 +27,21 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path/filepath"
) )
// TODO: rename to KeyStore when replacing existing KeyStore // TODO: rename to KeyStore when replacing existing KeyStore
type KeyStore2 interface { type KeyStore2 interface {
// create new key using io.Reader entropy source and optionally using auth string // create new key using io.Reader entropy source and optionally using auth string
GenerateNewKey(io.Reader, string) (*Key, error) GenerateNewKey(io.Reader, string) (*Key, error)
GetKey([]byte, string) (*Key, error) // key from addr and auth string GetKey(common.Address, string) (*Key, error) // key from addr and auth string
GetKeyAddresses() ([][]byte, error) // get all addresses GetKeyAddresses() ([]common.Address, error) // get all addresses
StoreKey(*Key, string) error // store key optionally using auth string StoreKey(*Key, string) error // store key optionally using auth string
DeleteKey([]byte, string) error // delete key by addr and auth string DeleteKey(common.Address, string) error // delete key by addr and auth string
} }
type keyStorePlain struct { type keyStorePlain struct {
@ -66,7 +67,7 @@ func GenerateNewKeyDefault(ks KeyStore2, rand io.Reader, auth string) (key *Key,
return key, err return key, err
} }
func (ks keyStorePlain) GetKey(keyAddr []byte, auth string) (key *Key, err error) { func (ks keyStorePlain) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr) fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -77,7 +78,7 @@ func (ks keyStorePlain) GetKey(keyAddr []byte, auth string) (key *Key, err error
return key, err return key, err
} }
func (ks keyStorePlain) GetKeyAddresses() (addresses [][]byte, err error) { func (ks keyStorePlain) GetKeyAddresses() (addresses []common.Address, err error) {
return GetKeyAddresses(ks.keysDirPath) return GetKeyAddresses(ks.keysDirPath)
} }
@ -90,21 +91,21 @@ func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) {
return err return err
} }
func (ks keyStorePlain) DeleteKey(keyAddr []byte, auth string) (err error) { func (ks keyStorePlain) DeleteKey(keyAddr common.Address, auth string) (err error) {
keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr)) keyDirPath := filepath.Join(ks.keysDirPath, keyAddr.Hex())
err = os.RemoveAll(keyDirPath) err = os.RemoveAll(keyDirPath)
return err return err
} }
func GetKeyFile(keysDirPath string, keyAddr []byte) (fileContent []byte, err error) { func GetKeyFile(keysDirPath string, keyAddr common.Address) (fileContent []byte, err error) {
fileName := hex.EncodeToString(keyAddr) fileName := hex.EncodeToString(keyAddr[:])
return ioutil.ReadFile(path.Join(keysDirPath, fileName, fileName)) return ioutil.ReadFile(filepath.Join(keysDirPath, fileName, fileName))
} }
func WriteKeyFile(addr []byte, keysDirPath string, content []byte) (err error) { func WriteKeyFile(addr common.Address, keysDirPath string, content []byte) (err error) {
addrHex := hex.EncodeToString(addr) addrHex := hex.EncodeToString(addr[:])
keyDirPath := path.Join(keysDirPath, addrHex) keyDirPath := filepath.Join(keysDirPath, addrHex)
keyFilePath := path.Join(keyDirPath, addrHex) keyFilePath := filepath.Join(keyDirPath, addrHex)
err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user
if err != nil { if err != nil {
return err return err
@ -112,7 +113,7 @@ func WriteKeyFile(addr []byte, keysDirPath string, content []byte) (err error) {
return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user
} }
func GetKeyAddresses(keysDirPath string) (addresses [][]byte, err error) { func GetKeyAddresses(keysDirPath string) (addresses []common.Address, err error) {
fileInfos, err := ioutil.ReadDir(keysDirPath) fileInfos, err := ioutil.ReadDir(keysDirPath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -122,7 +123,7 @@ func GetKeyAddresses(keysDirPath string) (addresses [][]byte, err error) {
if err != nil { if err != nil {
continue continue
} }
addresses = append(addresses, address) addresses = append(addresses, common.BytesToAddress(address))
} }
return addresses, err return addresses, err
} }

View File

@ -1,8 +1,8 @@
package crypto package crypto
import ( import (
"github.com/ethereum/go-ethereum/crypto/randentropy"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/randentropy"
"reflect" "reflect"
"testing" "testing"
) )

View File

@ -2,12 +2,8 @@ package randentropy
import ( import (
crand "crypto/rand" crand "crypto/rand"
"encoding/binary"
"github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/crypto/sha3"
"io" "io"
"os"
"strings"
"time"
) )
var Reader io.Reader = &randEntropy{} var Reader io.Reader = &randEntropy{}
@ -16,7 +12,7 @@ type randEntropy struct {
} }
func (*randEntropy) Read(bytes []byte) (n int, err error) { func (*randEntropy) Read(bytes []byte) (n int, err error) {
readBytes := GetEntropyMixed(len(bytes)) readBytes := GetEntropyCSPRNG(len(bytes))
copy(bytes, readBytes) copy(bytes, readBytes)
return len(bytes), nil return len(bytes), nil
} }
@ -29,40 +25,6 @@ func Sha3(data []byte) []byte {
return d.Sum(nil) return d.Sum(nil)
} }
// TODO: verify. this needs to be audited
// we start with crypt/rand, then XOR in additional entropy from OS
func GetEntropyMixed(n int) []byte {
startTime := time.Now().UnixNano()
// for each source, we take SHA3 of the source and use it as seed to math/rand
// then read bytes from it and XOR them onto the bytes read from crypto/rand
mainBuff := GetEntropyCSPRNG(n)
// 1. OS entropy sources
startTimeBytes := make([]byte, 32)
binary.PutVarint(startTimeBytes, startTime)
startTimeHash := Sha3(startTimeBytes)
mixBytes(mainBuff, startTimeHash)
pid := os.Getpid()
pidBytes := make([]byte, 32)
binary.PutUvarint(pidBytes, uint64(pid))
pidHash := Sha3(pidBytes)
mixBytes(mainBuff, pidHash)
osEnv := os.Environ()
osEnvBytes := []byte(strings.Join(osEnv, ""))
osEnvHash := Sha3(osEnvBytes)
mixBytes(mainBuff, osEnvHash)
// not all OS have hostname in env variables
osHostName, err := os.Hostname()
if err != nil {
osHostNameBytes := []byte(osHostName)
osHostNameHash := Sha3(osHostNameBytes)
mixBytes(mainBuff, osHostNameHash)
}
return mainBuff
}
func GetEntropyCSPRNG(n int) []byte { func GetEntropyCSPRNG(n int) []byte {
mainBuff := make([]byte, n) mainBuff := make([]byte, n)
_, err := io.ReadFull(crand.Reader, mainBuff) _, err := io.ReadFull(crand.Reader, mainBuff)
@ -71,14 +33,3 @@ func GetEntropyCSPRNG(n int) []byte {
} }
return mainBuff return mainBuff
} }
func mixBytes(buff []byte, mixBuff []byte) []byte {
bytesToMix := len(buff)
if bytesToMix > 32 {
bytesToMix = 32
}
for i := 0; i < bytesToMix; i++ {
buff[i] ^= mixBuff[i]
}
return buff
}

View File

@ -59,7 +59,7 @@ func GenerateKeyPair() ([]byte, []byte) {
const seckey_len = 32 const seckey_len = 32
var pubkey []byte = make([]byte, pubkey_len) var pubkey []byte = make([]byte, pubkey_len)
var seckey []byte = randentropy.GetEntropyMixed(seckey_len) var seckey []byte = randentropy.GetEntropyCSPRNG(seckey_len)
var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0])) var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0]))
var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0]))
@ -99,7 +99,7 @@ func GeneratePubKey(seckey []byte) ([]byte, error) {
} }
func Sign(msg []byte, seckey []byte) ([]byte, error) { func Sign(msg []byte, seckey []byte) ([]byte, error) {
nonce := randentropy.GetEntropyMixed(32) nonce := randentropy.GetEntropyCSPRNG(32)
var sig []byte = make([]byte, 65) var sig []byte = make([]byte, 65)
var recid C.int var recid C.int

View File

@ -14,7 +14,7 @@ const SigSize = 65 //64+1
func Test_Secp256_00(t *testing.T) { func Test_Secp256_00(t *testing.T) {
var nonce []byte = randentropy.GetEntropyMixed(32) //going to get bitcoins stolen! var nonce []byte = randentropy.GetEntropyCSPRNG(32) //going to get bitcoins stolen!
if len(nonce) != 32 { if len(nonce) != 32 {
t.Fatal() t.Fatal()
@ -52,7 +52,7 @@ func Test_Secp256_01(t *testing.T) {
//test size of messages //test size of messages
func Test_Secp256_02s(t *testing.T) { func Test_Secp256_02s(t *testing.T) {
pubkey, seckey := GenerateKeyPair() pubkey, seckey := GenerateKeyPair()
msg := randentropy.GetEntropyMixed(32) msg := randentropy.GetEntropyCSPRNG(32)
sig, _ := Sign(msg, seckey) sig, _ := Sign(msg, seckey)
CompactSigTest(sig) CompactSigTest(sig)
if sig == nil { if sig == nil {
@ -75,7 +75,7 @@ func Test_Secp256_02s(t *testing.T) {
//test signing message //test signing message
func Test_Secp256_02(t *testing.T) { func Test_Secp256_02(t *testing.T) {
pubkey1, seckey := GenerateKeyPair() pubkey1, seckey := GenerateKeyPair()
msg := randentropy.GetEntropyMixed(32) msg := randentropy.GetEntropyCSPRNG(32)
sig, _ := Sign(msg, seckey) sig, _ := Sign(msg, seckey)
if sig == nil { if sig == nil {
t.Fatal("Signature nil") t.Fatal("Signature nil")
@ -98,7 +98,7 @@ func Test_Secp256_02(t *testing.T) {
//test pubkey recovery //test pubkey recovery
func Test_Secp256_02a(t *testing.T) { func Test_Secp256_02a(t *testing.T) {
pubkey1, seckey1 := GenerateKeyPair() pubkey1, seckey1 := GenerateKeyPair()
msg := randentropy.GetEntropyMixed(32) msg := randentropy.GetEntropyCSPRNG(32)
sig, _ := Sign(msg, seckey1) sig, _ := Sign(msg, seckey1)
if sig == nil { if sig == nil {
@ -127,7 +127,7 @@ func Test_Secp256_02a(t *testing.T) {
func Test_Secp256_03(t *testing.T) { func Test_Secp256_03(t *testing.T) {
_, seckey := GenerateKeyPair() _, seckey := GenerateKeyPair()
for i := 0; i < TESTS; i++ { for i := 0; i < TESTS; i++ {
msg := randentropy.GetEntropyMixed(32) msg := randentropy.GetEntropyCSPRNG(32)
sig, _ := Sign(msg, seckey) sig, _ := Sign(msg, seckey)
CompactSigTest(sig) CompactSigTest(sig)
@ -143,7 +143,7 @@ func Test_Secp256_03(t *testing.T) {
func Test_Secp256_04(t *testing.T) { func Test_Secp256_04(t *testing.T) {
for i := 0; i < TESTS; i++ { for i := 0; i < TESTS; i++ {
pubkey1, seckey := GenerateKeyPair() pubkey1, seckey := GenerateKeyPair()
msg := randentropy.GetEntropyMixed(32) msg := randentropy.GetEntropyCSPRNG(32)
sig, _ := Sign(msg, seckey) sig, _ := Sign(msg, seckey)
CompactSigTest(sig) CompactSigTest(sig)
@ -166,7 +166,7 @@ func Test_Secp256_04(t *testing.T) {
// -SIPA look at this // -SIPA look at this
func randSig() []byte { func randSig() []byte {
sig := randentropy.GetEntropyMixed(65) sig := randentropy.GetEntropyCSPRNG(65)
sig[32] &= 0x70 sig[32] &= 0x70
sig[64] %= 4 sig[64] %= 4
return sig return sig
@ -174,7 +174,7 @@ func randSig() []byte {
func Test_Secp256_06a_alt0(t *testing.T) { func Test_Secp256_06a_alt0(t *testing.T) {
pubkey1, seckey := GenerateKeyPair() pubkey1, seckey := GenerateKeyPair()
msg := randentropy.GetEntropyMixed(32) msg := randentropy.GetEntropyCSPRNG(32)
sig, _ := Sign(msg, seckey) sig, _ := Sign(msg, seckey)
if sig == nil { if sig == nil {
@ -205,12 +205,12 @@ func Test_Secp256_06a_alt0(t *testing.T) {
func Test_Secp256_06b(t *testing.T) { func Test_Secp256_06b(t *testing.T) {
pubkey1, seckey := GenerateKeyPair() pubkey1, seckey := GenerateKeyPair()
msg := randentropy.GetEntropyMixed(32) msg := randentropy.GetEntropyCSPRNG(32)
sig, _ := Sign(msg, seckey) sig, _ := Sign(msg, seckey)
fail_count := 0 fail_count := 0
for i := 0; i < TESTS; i++ { for i := 0; i < TESTS; i++ {
msg = randentropy.GetEntropyMixed(32) msg = randentropy.GetEntropyCSPRNG(32)
pubkey2, _ := RecoverPubkey(msg, sig) pubkey2, _ := RecoverPubkey(msg, sig)
if bytes.Equal(pubkey1, pubkey2) == true { if bytes.Equal(pubkey1, pubkey2) == true {
t.Fail() t.Fail()

View File

@ -7,7 +7,6 @@ import (
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -145,7 +144,7 @@ func (cfg *Config) nodeKey() (*ecdsa.PrivateKey, error) {
return cfg.NodeKey, nil return cfg.NodeKey, nil
} }
// use persistent key if present // use persistent key if present
keyfile := path.Join(cfg.DataDir, "nodekey") keyfile := filepath.Join(cfg.DataDir, "nodekey")
key, err := crypto.LoadECDSA(keyfile) key, err := crypto.LoadECDSA(keyfile)
if err == nil { if err == nil {
return key, nil return key, nil
@ -207,29 +206,33 @@ func New(config *Config) (*Ethereum, error) {
logger.NewJSONsystem(config.DataDir, config.LogJSON) logger.NewJSONsystem(config.DataDir, config.LogJSON)
} }
// Let the database take 3/4 of the max open files (TODO figure out a way to get the actual limit of the open files)
const dbCount = 3
ethdb.OpenFileLimit = 256 / (dbCount + 1)
newdb := config.NewDB newdb := config.NewDB
if newdb == nil { if newdb == nil {
newdb = func(path string) (common.Database, error) { return ethdb.NewLDBDatabase(path) } newdb = func(path string) (common.Database, error) { return ethdb.NewLDBDatabase(path) }
} }
blockDb, err := newdb(path.Join(config.DataDir, "blockchain")) blockDb, err := newdb(filepath.Join(config.DataDir, "blockchain"))
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("blockchain db err: %v", err)
} }
stateDb, err := newdb(path.Join(config.DataDir, "state")) stateDb, err := newdb(filepath.Join(config.DataDir, "state"))
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("state db err: %v", err)
} }
extraDb, err := newdb(path.Join(config.DataDir, "extra")) extraDb, err := newdb(filepath.Join(config.DataDir, "extra"))
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("extra db err: %v", err)
} }
nodeDb := path.Join(config.DataDir, "nodes") nodeDb := filepath.Join(config.DataDir, "nodes")
// Perform database sanity checks // Perform database sanity checks
d, _ := blockDb.Get([]byte("ProtocolVersion")) d, _ := blockDb.Get([]byte("ProtocolVersion"))
protov := int(common.NewValue(d).Uint()) protov := int(common.NewValue(d).Uint())
if protov != config.ProtocolVersion && protov != 0 { if protov != config.ProtocolVersion && protov != 0 {
path := path.Join(config.DataDir, "blockchain") path := filepath.Join(config.DataDir, "blockchain")
return nil, fmt.Errorf("Database version mismatch. Protocol(%d / %d). `rm -rf %s`", protov, config.ProtocolVersion, path) return nil, fmt.Errorf("Database version mismatch. Protocol(%d / %d). `rm -rf %s`", protov, config.ProtocolVersion, path)
} }
saveProtocolVersion(blockDb, config.ProtocolVersion) saveProtocolVersion(blockDb, config.ProtocolVersion)
@ -267,7 +270,7 @@ func New(config *Config) (*Ethereum, error) {
eth.txPool = core.NewTxPool(eth.EventMux(), eth.chainManager.State, eth.chainManager.GasLimit) eth.txPool = core.NewTxPool(eth.EventMux(), eth.chainManager.State, eth.chainManager.GasLimit)
eth.blockProcessor = core.NewBlockProcessor(stateDb, extraDb, eth.pow, eth.txPool, eth.chainManager, eth.EventMux()) eth.blockProcessor = core.NewBlockProcessor(stateDb, extraDb, eth.pow, eth.txPool, eth.chainManager, eth.EventMux())
eth.chainManager.SetProcessor(eth.blockProcessor) eth.chainManager.SetProcessor(eth.blockProcessor)
eth.miner = miner.New(eth, eth.pow, config.MinerThreads) eth.miner = miner.New(eth, eth.pow)
eth.miner.SetGasPrice(config.GasPrice) eth.miner.SetGasPrice(config.GasPrice)
eth.protocolManager = NewProtocolManager(config.ProtocolVersion, config.NetworkId, eth.eventMux, eth.txPool, eth.chainManager, eth.downloader) eth.protocolManager = NewProtocolManager(config.ProtocolVersion, config.NetworkId, eth.eventMux, eth.txPool, eth.chainManager, eth.downloader)
@ -368,7 +371,7 @@ func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
s.chainManager.ResetWithGenesisBlock(gb) s.chainManager.ResetWithGenesisBlock(gb)
} }
func (s *Ethereum) StartMining() error { func (s *Ethereum) StartMining(threads int) error {
eb, err := s.Etherbase() eb, err := s.Etherbase()
if err != nil { if err != nil {
err = fmt.Errorf("Cannot start mining without etherbase address: %v", err) err = fmt.Errorf("Cannot start mining without etherbase address: %v", err)
@ -376,21 +379,24 @@ func (s *Ethereum) StartMining() error {
return err return err
} }
go s.miner.Start(eb) go s.miner.Start(eb, threads)
return nil return nil
} }
func (s *Ethereum) Etherbase() (eb common.Address, err error) { func (s *Ethereum) Etherbase() (eb common.Address, err error) {
eb = s.etherbase eb = s.etherbase
if (eb == common.Address{}) { if (eb == common.Address{}) {
var ebbytes []byte primary, err := s.accountManager.Primary()
ebbytes, err = s.accountManager.Primary() if err != nil {
eb = common.BytesToAddress(ebbytes) return eb, err
if (eb == common.Address{}) {
err = fmt.Errorf("no accounts found")
} }
if (primary == common.Address{}) {
err = fmt.Errorf("no accounts found")
return eb, err
}
eb = primary
} }
return return eb, nil
} }
func (s *Ethereum) StopMining() { s.miner.Stop() } func (s *Ethereum) StopMining() { s.miner.Stop() }
@ -451,6 +457,8 @@ func (s *Ethereum) Start() error {
return nil return nil
} }
// sync databases every minute. If flushing fails we exit immediatly. The system
// may not continue under any circumstances.
func (s *Ethereum) syncDatabases() { func (s *Ethereum) syncDatabases() {
ticker := time.NewTicker(1 * time.Minute) ticker := time.NewTicker(1 * time.Minute)
done: done:
@ -459,13 +467,13 @@ done:
case <-ticker.C: case <-ticker.C:
// don't change the order of database flushes // don't change the order of database flushes
if err := s.extraDb.Flush(); err != nil { if err := s.extraDb.Flush(); err != nil {
glog.V(logger.Error).Infof("error: flush extraDb: %v\n", err) glog.Fatalf("fatal error: flush extraDb: %v (Restart your node. We are aware of this issue)\n", err)
} }
if err := s.stateDb.Flush(); err != nil { if err := s.stateDb.Flush(); err != nil {
glog.V(logger.Error).Infof("error: flush stateDb: %v\n", err) glog.Fatalf("fatal error: flush stateDb: %v (Restart your node. We are aware of this issue)\n", err)
} }
if err := s.blockDb.Flush(); err != nil { if err := s.blockDb.Flush(); err != nil {
glog.V(logger.Error).Infof("error: flush blockDb: %v\n", err) glog.Fatalf("fatal error: flush blockDb: %v (Restart your node. We are aware of this issue)\n", err)
} }
case <-s.shutdownChan: case <-s.shutdownChan:
break done break done
@ -537,7 +545,7 @@ func (self *Ethereum) syncAccounts(tx *types.Transaction) {
return return
} }
if self.accountManager.HasAccount(from.Bytes()) { if self.accountManager.HasAccount(from) {
if self.chainManager.TxState().GetNonce(from) < tx.Nonce() { if self.chainManager.TxState().GetNonce(from) < tx.Nonce() {
self.chainManager.TxState().SetNonce(from, tx.Nonce()) self.chainManager.TxState().SetNonce(from, tx.Nonce())
} }

View File

@ -28,7 +28,7 @@ var (
errUnknownPeer = errors.New("peer's unknown or unhealthy") errUnknownPeer = errors.New("peer's unknown or unhealthy")
errBadPeer = errors.New("action from bad peer ignored") errBadPeer = errors.New("action from bad peer ignored")
errNoPeers = errors.New("no peers to keep download active") errNoPeers = errors.New("no peers to keep download active")
errPendingQueue = errors.New("pending items in queue") ErrPendingQueue = errors.New("pending items in queue")
ErrTimeout = errors.New("timeout") ErrTimeout = errors.New("timeout")
errEmptyHashSet = errors.New("empty hash set by peer") errEmptyHashSet = errors.New("empty hash set by peer")
errPeersUnavailable = errors.New("no peers available or all peers tried for block download process") errPeersUnavailable = errors.New("no peers available or all peers tried for block download process")
@ -49,12 +49,6 @@ type blockPack struct {
blocks []*types.Block blocks []*types.Block
} }
type syncPack struct {
peer *peer
hash common.Hash
ignoreInitial bool
}
type hashPack struct { type hashPack struct {
peerId string peerId string
hashes []common.Hash hashes []common.Hash
@ -63,7 +57,7 @@ type hashPack struct {
type Downloader struct { type Downloader struct {
mu sync.RWMutex mu sync.RWMutex
queue *queue queue *queue
peers peers peers *peerSet
activePeer string activePeer string
// Callbacks // Callbacks
@ -83,7 +77,7 @@ type Downloader struct {
func New(hasBlock hashCheckFn, getBlock getBlockFn) *Downloader { func New(hasBlock hashCheckFn, getBlock getBlockFn) *Downloader {
downloader := &Downloader{ downloader := &Downloader{
queue: newQueue(), queue: newQueue(),
peers: make(peers), peers: newPeerSet(),
hasBlock: hasBlock, hasBlock: hasBlock,
getBlock: getBlock, getBlock: getBlock,
newPeerCh: make(chan *peer, 1), newPeerCh: make(chan *peer, 1),
@ -98,29 +92,26 @@ func (d *Downloader) Stats() (current int, max int) {
return d.queue.Size() return d.queue.Size()
} }
func (d *Downloader) RegisterPeer(id string, hash common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) error { // RegisterPeer injects a new download peer into the set of block source to be
d.mu.Lock() // used for fetching hashes and blocks from.
defer d.mu.Unlock() func (d *Downloader) RegisterPeer(id string, head common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) error {
glog.V(logger.Detail).Infoln("Registering peer", id)
glog.V(logger.Detail).Infoln("Register peer", id) if err := d.peers.Register(newPeer(id, head, getHashes, getBlocks)); err != nil {
glog.V(logger.Error).Infoln("Register failed:", err)
// Create a new peer and add it to the list of known peers return err
peer := newPeer(id, hash, getHashes, getBlocks) }
// add peer to our peer set
d.peers[id] = peer
// broadcast new peer
return nil return nil
} }
// UnregisterPeer unregisters a peer. This will prevent any action from the specified peer. // UnregisterPeer remove a peer from the known list, preventing any action from
func (d *Downloader) UnregisterPeer(id string) { // the specified peer.
d.mu.Lock() func (d *Downloader) UnregisterPeer(id string) error {
defer d.mu.Unlock() glog.V(logger.Detail).Infoln("Unregistering peer", id)
if err := d.peers.Unregister(id); err != nil {
glog.V(logger.Detail).Infoln("Unregister peer", id) glog.V(logger.Error).Infoln("Unregister failed:", err)
return err
delete(d.peers, id) }
return nil
} }
// Synchronise will select the peer and use it for synchronising. If an empty string is given // Synchronise will select the peer and use it for synchronising. If an empty string is given
@ -138,17 +129,18 @@ func (d *Downloader) Synchronise(id string, hash common.Hash) error {
// Abort if the queue still contains some leftover data // Abort if the queue still contains some leftover data
if _, cached := d.queue.Size(); cached > 0 && d.queue.GetHeadBlock() != nil { if _, cached := d.queue.Size(); cached > 0 && d.queue.GetHeadBlock() != nil {
return errPendingQueue return ErrPendingQueue
} }
// Reset the queue to clean any internal leftover state // Reset the queue and peer set to clean any internal leftover state
d.queue.Reset() d.queue.Reset()
d.peers.Reset()
// Retrieve the origin peer and initiate the downloading process // Retrieve the origin peer and initiate the downloading process
p := d.peers[id] p := d.peers.Peer(id)
if p == nil { if p == nil {
return errUnknownPeer return errUnknownPeer
} }
return d.getFromPeer(p, hash, false) return d.syncWithPeer(p, hash)
} }
// TakeBlocks takes blocks from the queue and yields them to the blockTaker handler // TakeBlocks takes blocks from the queue and yields them to the blockTaker handler
@ -167,7 +159,9 @@ func (d *Downloader) Has(hash common.Hash) bool {
return d.queue.Has(hash) return d.queue.Has(hash)
} }
func (d *Downloader) getFromPeer(p *peer, hash common.Hash, ignoreInitial bool) (err error) { // syncWithPeer starts a block synchronization based on the hash chain from the
// specified peer and head hash.
func (d *Downloader) syncWithPeer(p *peer, hash common.Hash) (err error) {
d.activePeer = p.id d.activePeer = p.id
defer func() { defer func() {
// reset on error // reset on error
@ -177,21 +171,12 @@ func (d *Downloader) getFromPeer(p *peer, hash common.Hash, ignoreInitial bool)
}() }()
glog.V(logger.Debug).Infoln("Synchronizing with the network using:", p.id) glog.V(logger.Debug).Infoln("Synchronizing with the network using:", p.id)
// Start the fetcher. This will block the update entirely if err = d.fetchHashes(p, hash); err != nil {
// interupts need to be send to the appropriate channels
// respectively.
if err = d.startFetchingHashes(p, hash, ignoreInitial); err != nil {
return err return err
} }
if err = d.fetchBlocks(); err != nil {
// Start fetching blocks in paralel. The strategy is simple
// take any available peers, seserve a chunk for each peer available,
// let the peer deliver the chunkn and periodically check if a peer
// has timedout.
if err = d.startFetchingBlocks(p); err != nil {
return err return err
} }
glog.V(logger.Debug).Infoln("Synchronization completed") glog.V(logger.Debug).Infoln("Synchronization completed")
return nil return nil
@ -234,17 +219,14 @@ blockDone:
} }
// XXX Make synchronous // XXX Make synchronous
func (d *Downloader) startFetchingHashes(p *peer, h common.Hash, ignoreInitial bool) error { func (d *Downloader) fetchHashes(p *peer, h common.Hash) error {
glog.V(logger.Debug).Infof("Downloading hashes (%x) from %s", h[:4], p.id) glog.V(logger.Debug).Infof("Downloading hashes (%x) from %s", h[:4], p.id)
start := time.Now() start := time.Now()
// We ignore the initial hash in some cases (e.g. we received a block without it's parent) // Add the hash to the queue first
// In such circumstances we don't need to download the block so don't add it to the queue. d.queue.Insert([]common.Hash{h})
if !ignoreInitial {
// Add the hash to the queue first
d.queue.Insert([]common.Hash{h})
}
// Get the first batch of hashes // Get the first batch of hashes
p.getHashes(h) p.getHashes(h)
@ -308,20 +290,18 @@ out:
// Attempt to find a new peer by checking inclusion of peers best hash in our // Attempt to find a new peer by checking inclusion of peers best hash in our
// already fetched hash list. This can't guarantee 100% correctness but does // already fetched hash list. This can't guarantee 100% correctness but does
// a fair job. This is always either correct or false incorrect. // a fair job. This is always either correct or false incorrect.
for id, peer := range d.peers { for _, peer := range d.peers.AllPeers() {
if d.queue.Has(peer.recentHash) && !attemptedPeers[id] { if d.queue.Has(peer.head) && !attemptedPeers[p.id] {
p = peer p = peer
break break
} }
} }
// if all peers have been tried, abort the process entirely or if the hash is // if all peers have been tried, abort the process entirely or if the hash is
// the zero hash. // the zero hash.
if p == nil || (hash == common.Hash{}) { if p == nil || (hash == common.Hash{}) {
d.queue.Reset() d.queue.Reset()
return ErrTimeout return ErrTimeout
} }
// set p to the active peer. this will invalidate any hashes that may be returned // set p to the active peer. this will invalidate any hashes that may be returned
// by our previous (delayed) peer. // by our previous (delayed) peer.
activePeer = p activePeer = p
@ -334,14 +314,11 @@ out:
return nil return nil
} }
func (d *Downloader) startFetchingBlocks(p *peer) error { // fetchBlocks iteratively downloads the entire schedules block-chain, taking
// any available peers, reserving a chunk of blocks for each, wait for delivery
// and periodically checking for timeouts.
func (d *Downloader) fetchBlocks() error {
glog.V(logger.Debug).Infoln("Downloading", d.queue.Pending(), "block(s)") glog.V(logger.Debug).Infoln("Downloading", d.queue.Pending(), "block(s)")
// Defer the peer reset. This will empty the peer requested set
// and makes sure there are no lingering peers with an incorrect
// state
defer d.peers.reset()
start := time.Now() start := time.Now()
// default ticker for re-fetching blocks every now and then // default ticker for re-fetching blocks every now and then
@ -354,19 +331,19 @@ out:
case blockPack := <-d.blockCh: case blockPack := <-d.blockCh:
// If the peer was previously banned and failed to deliver it's pack // If the peer was previously banned and failed to deliver it's pack
// in a reasonable time frame, ignore it's message. // in a reasonable time frame, ignore it's message.
if d.peers[blockPack.peerId] != nil { if peer := d.peers.Peer(blockPack.peerId); peer != nil {
err := d.queue.Deliver(blockPack.peerId, blockPack.blocks) // Deliver the received chunk of blocks, but drop the peer if invalid
if err != nil { if err := d.queue.Deliver(blockPack.peerId, blockPack.blocks); err != nil {
glog.V(logger.Debug).Infof("deliver failed for peer %s: %v\n", blockPack.peerId, err) glog.V(logger.Debug).Infof("Failed delivery for peer %s: %v\n", blockPack.peerId, err)
// FIXME d.UnregisterPeer(blockPack.peerId) peer.Demote()
break break
} }
if glog.V(logger.Debug) { if glog.V(logger.Debug) {
glog.Infof("adding %d blocks from: %s\n", len(blockPack.blocks), blockPack.peerId) glog.Infof("Added %d blocks from: %s\n", len(blockPack.blocks), blockPack.peerId)
} }
d.peers[blockPack.peerId].promote() // Promote the peer and update it's idle state
d.peers.setState(blockPack.peerId, idleState) peer.Promote()
peer.SetIdle()
} }
case <-ticker.C: case <-ticker.C:
// Check for bad peers. Bad peers may indicate a peer not responding // Check for bad peers. Bad peers may indicate a peer not responding
@ -381,13 +358,12 @@ out:
// 1) Time for them to respond; // 1) Time for them to respond;
// 2) Measure their speed; // 2) Measure their speed;
// 3) Amount and availability. // 3) Amount and availability.
if peer := d.peers[pid]; peer != nil { if peer := d.peers.Peer(pid); peer != nil {
peer.demote() peer.Demote()
peer.reset()
} }
} }
// After removing bad peers make sure we actually have sufficient peer left to keep downloading // After removing bad peers make sure we actually have sufficient peer left to keep downloading
if len(d.peers) == 0 { if d.peers.Len() == 0 {
d.queue.Reset() d.queue.Reset()
return errNoPeers return errNoPeers
} }
@ -398,31 +374,33 @@ out:
if d.queue.Throttle() { if d.queue.Throttle() {
continue continue
} }
// Send a download request to all idle peers, until throttled
availablePeers := d.peers.get(idleState) idlePeers := d.peers.IdlePeers()
for _, peer := range availablePeers { for _, peer := range idlePeers {
// Short circuit if throttling activated since above
if d.queue.Throttle() {
break
}
// Get a possible chunk. If nil is returned no chunk // Get a possible chunk. If nil is returned no chunk
// could be returned due to no hashes available. // could be returned due to no hashes available.
request := d.queue.Reserve(peer, maxBlockFetch) request := d.queue.Reserve(peer, maxBlockFetch)
if request == nil { if request == nil {
continue continue
} }
// XXX make fetch blocking.
// Fetch the chunk and check for error. If the peer was somehow // Fetch the chunk and check for error. If the peer was somehow
// already fetching a chunk due to a bug, it will be returned to // already fetching a chunk due to a bug, it will be returned to
// the queue // the queue
if err := peer.fetch(request); err != nil { if err := peer.Fetch(request); err != nil {
// log for tracing glog.V(logger.Error).Infof("Peer %s received double work\n", peer.id)
glog.V(logger.Debug).Infof("peer %s received double work (state = %v)\n", peer.id, peer.state)
d.queue.Cancel(request) d.queue.Cancel(request)
} }
} }
// make sure that we have peers available for fetching. If all peers have been tried // Make sure that we have peers available for fetching. If all peers have been tried
// and all failed throw an error // and all failed throw an error
if d.queue.InFlight() == 0 { if d.queue.InFlight() == 0 {
d.queue.Reset() d.queue.Reset()
return fmt.Errorf("%v peers avaialable = %d. total peers = %d. hashes needed = %d", errPeersUnavailable, len(availablePeers), len(d.peers), d.queue.Pending()) return fmt.Errorf("%v peers available = %d. total peers = %d. hashes needed = %d", errPeersUnavailable, len(idlePeers), d.peers.Len(), d.queue.Pending())
} }
} else if d.queue.InFlight() == 0 { } else if d.queue.InFlight() == 0 {

View File

@ -229,7 +229,7 @@ func TestThrottling(t *testing.T) {
minDesiredPeerCount = 4 minDesiredPeerCount = 4
blockTtl = 1 * time.Second blockTtl = 1 * time.Second
targetBlocks := 4 * blockCacheLimit targetBlocks := 16 * blockCacheLimit
hashes := createHashes(0, targetBlocks) hashes := createHashes(0, targetBlocks)
blocks := createBlocksFromHashes(hashes) blocks := createBlocksFromHashes(hashes)
tester := newTester(t, hashes, blocks) tester := newTester(t, hashes, blocks)
@ -256,6 +256,7 @@ func TestThrottling(t *testing.T) {
return return
default: default:
took = append(took, tester.downloader.TakeBlocks()...) took = append(took, tester.downloader.TakeBlocks()...)
time.Sleep(time.Millisecond)
} }
} }
}() }()

View File

@ -1,63 +1,35 @@
// Contains the active peer-set of the downloader, maintaining both failures
// as well as reputation metrics to prioritize the block retrievals.
package downloader package downloader
import ( import (
"errors" "errors"
"sync" "sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"gopkg.in/fatih/set.v0" "gopkg.in/fatih/set.v0"
) )
const (
workingState = 2
idleState = 4
)
type hashFetcherFn func(common.Hash) error type hashFetcherFn func(common.Hash) error
type blockFetcherFn func([]common.Hash) error type blockFetcherFn func([]common.Hash) error
// XXX make threadsafe!!!! var (
type peers map[string]*peer errAlreadyFetching = errors.New("already fetching blocks from peer")
errAlreadyRegistered = errors.New("peer is already registered")
errNotRegistered = errors.New("peer is not registered")
)
func (p peers) reset() { // peer represents an active peer from which hashes and blocks are retrieved.
for _, peer := range p {
peer.reset()
}
}
func (p peers) get(state int) []*peer {
var peers []*peer
for _, peer := range p {
peer.mu.RLock()
if peer.state == state {
peers = append(peers, peer)
}
peer.mu.RUnlock()
}
return peers
}
func (p peers) setState(id string, state int) {
if peer, exist := p[id]; exist {
peer.mu.Lock()
defer peer.mu.Unlock()
peer.state = state
}
}
func (p peers) getPeer(id string) *peer {
return p[id]
}
// peer represents an active peer
type peer struct { type peer struct {
state int // Peer state (working, idle) id string // Unique identifier of the peer
rep int // TODO peer reputation head common.Hash // Hash of the peers latest known block
mu sync.RWMutex idle int32 // Current activity state of the peer (idle = 0, active = 1)
id string rep int32 // Simple peer reputation (not used currently)
recentHash common.Hash
mu sync.RWMutex
ignored *set.Set ignored *set.Set
@ -65,31 +37,31 @@ type peer struct {
getBlocks blockFetcherFn getBlocks blockFetcherFn
} }
// create a new peer // newPeer create a new downloader peer, with specific hash and block retrieval
func newPeer(id string, hash common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) *peer { // mechanisms.
func newPeer(id string, head common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) *peer {
return &peer{ return &peer{
id: id, id: id,
recentHash: hash, head: head,
getHashes: getHashes, getHashes: getHashes,
getBlocks: getBlocks, getBlocks: getBlocks,
state: idleState, ignored: set.New(),
ignored: set.New(),
} }
} }
// fetch a chunk using the peer // Reset clears the internal state of a peer entity.
func (p *peer) fetch(request *fetchRequest) error { func (p *peer) Reset() {
p.mu.Lock() atomic.StoreInt32(&p.idle, 0)
defer p.mu.Unlock() p.ignored.Clear()
}
if p.state == workingState { // Fetch sends a block retrieval request to the remote peer.
return errors.New("peer already fetching chunk") func (p *peer) Fetch(request *fetchRequest) error {
// Short circuit if the peer is already fetching
if !atomic.CompareAndSwapInt32(&p.idle, 0, 1) {
return errAlreadyFetching
} }
// Convert the hash set to a retrievable slice
// set working state
p.state = workingState
// Convert the hash set to a fetchable slice
hashes := make([]common.Hash, 0, len(request.Hashes)) hashes := make([]common.Hash, 0, len(request.Hashes))
for hash, _ := range request.Hashes { for hash, _ := range request.Hashes {
hashes = append(hashes, hash) hashes = append(hashes, hash)
@ -99,27 +71,127 @@ func (p *peer) fetch(request *fetchRequest) error {
return nil return nil
} }
// promote increases the peer's reputation // SetIdle sets the peer to idle, allowing it to execute new retrieval requests.
func (p *peer) promote() { func (p *peer) SetIdle() {
p.mu.Lock() atomic.StoreInt32(&p.idle, 0)
defer p.mu.Unlock()
p.rep++
} }
// demote decreases the peer's reputation or leaves it at 0 // Promote increases the peer's reputation.
func (p *peer) demote() { func (p *peer) Promote() {
p.mu.Lock() atomic.AddInt32(&p.rep, 1)
defer p.mu.Unlock() }
if p.rep > 1 { // Demote decreases the peer's reputation or leaves it at 0.
p.rep -= 2 func (p *peer) Demote() {
} else { for {
p.rep = 0 // Calculate the new reputation value
prev := atomic.LoadInt32(&p.rep)
next := prev / 2
// Try to update the old value
if atomic.CompareAndSwapInt32(&p.rep, prev, next) {
return
}
} }
} }
func (p *peer) reset() { // peerSet represents the collection of active peer participating in the block
p.state = idleState // download procedure.
p.ignored.Clear() type peerSet struct {
peers map[string]*peer
lock sync.RWMutex
}
// newPeerSet creates a new peer set top track the active download sources.
func newPeerSet() *peerSet {
return &peerSet{
peers: make(map[string]*peer),
}
}
// Reset iterates over the current peer set, and resets each of the known peers
// to prepare for a next batch of block retrieval.
func (ps *peerSet) Reset() {
ps.lock.RLock()
defer ps.lock.RUnlock()
for _, peer := range ps.peers {
peer.Reset()
}
}
// Register injects a new peer into the working set, or returns an error if the
// peer is already known.
func (ps *peerSet) Register(p *peer) error {
ps.lock.Lock()
defer ps.lock.Unlock()
if _, ok := ps.peers[p.id]; ok {
return errAlreadyRegistered
}
ps.peers[p.id] = p
return nil
}
// Unregister removes a remote peer from the active set, disabling any further
// actions to/from that particular entity.
func (ps *peerSet) Unregister(id string) error {
ps.lock.Lock()
defer ps.lock.Unlock()
if _, ok := ps.peers[id]; !ok {
return errNotRegistered
}
delete(ps.peers, id)
return nil
}
// Peer retrieves the registered peer with the given id.
func (ps *peerSet) Peer(id string) *peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
return ps.peers[id]
}
// Len returns if the current number of peers in the set.
func (ps *peerSet) Len() int {
ps.lock.RLock()
defer ps.lock.RUnlock()
return len(ps.peers)
}
// AllPeers retrieves a flat list of all the peers within the set.
func (ps *peerSet) AllPeers() []*peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
list := make([]*peer, 0, len(ps.peers))
for _, p := range ps.peers {
list = append(list, p)
}
return list
}
// IdlePeers retrieves a flat list of all the currently idle peers within the
// active peer set, ordered by their reputation.
func (ps *peerSet) IdlePeers() []*peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
list := make([]*peer, 0, len(ps.peers))
for _, p := range ps.peers {
if atomic.LoadInt32(&p.idle) == 0 {
list = append(list, p)
}
}
for i := 0; i < len(list); i++ {
for j := i + 1; j < len(list); j++ {
if atomic.LoadInt32(&list[i].rep) < atomic.LoadInt32(&list[j].rep) {
list[i], list[j] = list[j], list[i]
}
}
}
return list
} }

View File

@ -1,3 +1,6 @@
// Contains the block download scheduler to collect download tasks and schedule
// them in an ordered, and throttled way.
package downloader package downloader
import ( import (
@ -8,6 +11,8 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"gopkg.in/karalabe/cookiejar.v2/collections/prque" "gopkg.in/karalabe/cookiejar.v2/collections/prque"
) )
@ -126,6 +131,10 @@ func (q *queue) Insert(hashes []common.Hash) {
for i, hash := range hashes { for i, hash := range hashes {
index := q.hashCounter + i index := q.hashCounter + i
if old, ok := q.hashPool[hash]; ok {
glog.V(logger.Warn).Infof("Hash %x already scheduled at index %v", hash, old)
continue
}
q.hashPool[hash] = index q.hashPool[hash] = index
q.hashQueue.Push(hash, float32(index)) // Highest gets schedules first q.hashQueue.Push(hash, float32(index)) // Highest gets schedules first
} }

View File

@ -381,7 +381,7 @@ func (pm *ProtocolManager) BroadcastTx(hash common.Hash, tx *types.Transaction)
} }
} }
// Broadcast block to peer set // Broadcast block to peer set
peers = peers[:int(math.Sqrt(float64(len(peers))))] //FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
for _, peer := range peers { for _, peer := range peers {
peer.sendTransaction(tx) peer.sendTransaction(tx)
} }

View File

@ -98,7 +98,8 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
case downloader.ErrTimeout: case downloader.ErrTimeout:
glog.V(logger.Debug).Infof("Removing peer %v due to sync timeout", peer.id) glog.V(logger.Debug).Infof("Removing peer %v due to sync timeout", peer.id)
pm.removePeer(peer) pm.removePeer(peer)
case downloader.ErrPendingQueue:
glog.V(logger.Debug).Infoln("Synchronisation aborted:", err)
default: default:
glog.V(logger.Warn).Infof("Synchronisation failed: %v", err) glog.V(logger.Warn).Infof("Synchronisation failed: %v", err)
} }

View File

@ -11,7 +11,7 @@ import (
"github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/opt"
) )
const openFileLimit = 128 var OpenFileLimit = 64
type LDBDatabase struct { type LDBDatabase struct {
fn string fn string
@ -26,7 +26,7 @@ type LDBDatabase struct {
func NewLDBDatabase(file string) (*LDBDatabase, error) { func NewLDBDatabase(file string) (*LDBDatabase, error) {
// Open the db // Open the db
db, err := leveldb.OpenFile(file, &opt.Options{OpenFilesCacheCapacity: openFileLimit}) db, err := leveldb.OpenFile(file, &opt.Options{OpenFilesCacheCapacity: OpenFileLimit})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,13 +2,13 @@ package ethdb
import ( import (
"os" "os"
"path" "path/filepath"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
func newDb() *LDBDatabase { func newDb() *LDBDatabase {
file := path.Join("/", "tmp", "ldbtesttmpfile") file := filepath.Join("/", "tmp", "ldbtesttmpfile")
if common.FileExist(file) { if common.FileExist(file) {
os.RemoveAll(file) os.RemoveAll(file)
} }

View File

@ -8,7 +8,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path" "path/filepath"
"strings" "strings"
) )
@ -35,7 +35,7 @@ func main() {
m := make(map[string]setting) m := make(map[string]setting)
json.Unmarshal(content, &m) json.Unmarshal(content, &m)
filepath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "params", os.Args[2]) filepath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "params", os.Args[2])
output, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, os.ModePerm /*0777*/) output, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, os.ModePerm /*0777*/)
if err != nil { if err != nil {
fatal("error opening file for writing %v\n", err) fatal("error opening file for writing %v\n", err)

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ import (
"github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/pow"
) )
type CpuMiner struct { type CpuAgent struct {
chMu sync.Mutex chMu sync.Mutex
c chan *types.Block c chan *types.Block
quit chan struct{} quit chan struct{}
@ -21,8 +21,8 @@ type CpuMiner struct {
pow pow.PoW pow pow.PoW
} }
func NewCpuMiner(index int, pow pow.PoW) *CpuMiner { func NewCpuAgent(index int, pow pow.PoW) *CpuAgent {
miner := &CpuMiner{ miner := &CpuAgent{
pow: pow, pow: pow,
index: index, index: index,
} }
@ -30,16 +30,16 @@ func NewCpuMiner(index int, pow pow.PoW) *CpuMiner {
return miner return miner
} }
func (self *CpuMiner) Work() chan<- *types.Block { return self.c } func (self *CpuAgent) Work() chan<- *types.Block { return self.c }
func (self *CpuMiner) Pow() pow.PoW { return self.pow } func (self *CpuAgent) Pow() pow.PoW { return self.pow }
func (self *CpuMiner) SetReturnCh(ch chan<- *types.Block) { self.returnCh = ch } func (self *CpuAgent) SetReturnCh(ch chan<- *types.Block) { self.returnCh = ch }
func (self *CpuMiner) Stop() { func (self *CpuAgent) Stop() {
close(self.quit) close(self.quit)
close(self.quitCurrentOp) close(self.quitCurrentOp)
} }
func (self *CpuMiner) Start() { func (self *CpuAgent) Start() {
self.quit = make(chan struct{}) self.quit = make(chan struct{})
self.quitCurrentOp = make(chan struct{}, 1) self.quitCurrentOp = make(chan struct{}, 1)
self.c = make(chan *types.Block, 1) self.c = make(chan *types.Block, 1)
@ -47,7 +47,7 @@ func (self *CpuMiner) Start() {
go self.update() go self.update()
} }
func (self *CpuMiner) update() { func (self *CpuAgent) update() {
out: out:
for { for {
select { select {
@ -76,7 +76,7 @@ done:
} }
} }
func (self *CpuMiner) mine(block *types.Block) { func (self *CpuAgent) mine(block *types.Block) {
glog.V(logger.Debug).Infof("(re)started agent[%d]. mining...\n", self.index) glog.V(logger.Debug).Infof("(re)started agent[%d]. mining...\n", self.index)
// Reset the channel // Reset the channel
@ -95,6 +95,6 @@ func (self *CpuMiner) mine(block *types.Block) {
} }
} }
func (self *CpuMiner) GetHashRate() int64 { func (self *CpuAgent) GetHashRate() int64 {
return self.pow.GetHashrate() return self.pow.GetHashrate()
} }

View File

@ -7,6 +7,8 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/pow"
) )
@ -21,16 +23,8 @@ type Miner struct {
pow pow.PoW pow pow.PoW
} }
func New(eth core.Backend, pow pow.PoW, minerThreads int) *Miner { func New(eth core.Backend, pow pow.PoW) *Miner {
// note: minerThreads is currently ignored because return &Miner{eth: eth, pow: pow, worker: newWorker(common.Address{}, eth)}
// ethash is not thread safe.
miner := &Miner{eth: eth, pow: pow, worker: newWorker(common.Address{}, eth)}
for i := 0; i < minerThreads; i++ {
miner.worker.register(NewCpuMiner(i, pow))
}
miner.threads = minerThreads
return miner
} }
func (self *Miner) Mining() bool { func (self *Miner) Mining() bool {
@ -46,13 +40,27 @@ func (m *Miner) SetGasPrice(price *big.Int) {
m.worker.gasPrice = price m.worker.gasPrice = price
} }
func (self *Miner) Start(coinbase common.Address) { func (self *Miner) Start(coinbase common.Address, threads int) {
self.mining = true self.mining = true
for i := 0; i < threads; i++ {
self.worker.register(NewCpuAgent(i, self.pow))
}
self.threads = threads
glog.V(logger.Info).Infof("Starting mining operation (CPU=%d TOT=%d)\n", threads, len(self.worker.agents))
self.worker.coinbase = coinbase self.worker.coinbase = coinbase
self.worker.start() self.worker.start()
self.worker.commitNewWork() self.worker.commitNewWork()
} }
func (self *Miner) Stop() {
self.worker.stop()
self.mining = false
}
func (self *Miner) Register(agent Agent) { func (self *Miner) Register(agent Agent) {
if self.mining { if self.mining {
agent.Start() agent.Start()
@ -61,11 +69,6 @@ func (self *Miner) Register(agent Agent) {
self.worker.register(agent) self.worker.register(agent)
} }
func (self *Miner) Stop() {
self.mining = false
self.worker.stop()
}
func (self *Miner) HashRate() int64 { func (self *Miner) HashRate() int64 {
return self.worker.HashRate() return self.worker.HashRate()
} }

View File

@ -64,13 +64,13 @@ func (a *RemoteAgent) GetWork() [3]string {
res[0] = a.work.HashNoNonce().Hex() res[0] = a.work.HashNoNonce().Hex()
seedHash, _ := ethash.GetSeedHash(a.currentWork.NumberU64()) seedHash, _ := ethash.GetSeedHash(a.currentWork.NumberU64())
res[1] = common.Bytes2Hex(seedHash) res[1] = common.BytesToHash(seedHash).Hex()
// Calculate the "target" to be returned to the external miner // Calculate the "target" to be returned to the external miner
n := big.NewInt(1) n := big.NewInt(1)
n.Lsh(n, 255) n.Lsh(n, 255)
n.Div(n, a.work.Difficulty()) n.Div(n, a.work.Difficulty())
n.Lsh(n, 1) n.Lsh(n, 1)
res[2] = common.Bytes2Hex(n.Bytes()) res[2] = common.BytesToHash(n.Bytes()).Hex()
} }
return res return res

View File

@ -7,6 +7,7 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
@ -20,15 +21,41 @@ import (
var jsonlogger = logger.NewJsonLogger() var jsonlogger = logger.NewJsonLogger()
type environment struct { // Work holds the current work
totalUsedGas *big.Int type Work struct {
state *state.StateDB Number uint64
coinbase *state.StateObject Nonce uint64
block *types.Block MixDigest []byte
family *set.Set SeedHash []byte
uncles *set.Set
} }
// Agent can register themself with the worker
type Agent interface {
Work() chan<- *types.Block
SetReturnCh(chan<- *types.Block)
Stop()
Start()
GetHashRate() int64
}
// environment is the workers current environment and holds
// all of the current state information
type environment struct {
totalUsedGas *big.Int // total gas usage in the cycle
state *state.StateDB // apply state changes here
coinbase *state.StateObject // the miner's account
block *types.Block // the new block
family *set.Set // family set (used for checking uncles)
uncles *set.Set // uncle set
remove *set.Set // tx which will be removed
tcount int // tx count in cycle
ignoredTransactors *set.Set
lowGasTransactors *set.Set
ownedAccounts *set.Set
lowGasTxs types.Transactions
}
// env returns a new environment for the current cycle
func env(block *types.Block, eth core.Backend) *environment { func env(block *types.Block, eth core.Backend) *environment {
state := state.New(block.Root(), eth.StateDb()) state := state.New(block.Root(), eth.StateDb())
env := &environment{ env := &environment{
@ -43,21 +70,7 @@ func env(block *types.Block, eth core.Backend) *environment {
return env return env
} }
type Work struct { // worker is the main object which takes care of applying messages to the new state
Number uint64
Nonce uint64
MixDigest []byte
SeedHash []byte
}
type Agent interface {
Work() chan<- *types.Block
SetReturnCh(chan<- *types.Block)
Stop()
Start()
GetHashRate() int64
}
type worker struct { type worker struct {
mu sync.Mutex mu sync.Mutex
@ -128,12 +141,12 @@ func (self *worker) start() {
self.mu.Lock() self.mu.Lock()
defer self.mu.Unlock() defer self.mu.Unlock()
atomic.StoreInt32(&self.mining, 1)
// spin up agents // spin up agents
for _, agent := range self.agents { for _, agent := range self.agents {
agent.Start() agent.Start()
} }
atomic.StoreInt32(&self.mining, 1)
} }
func (self *worker) stop() { func (self *worker) stop() {
@ -141,10 +154,16 @@ func (self *worker) stop() {
defer self.mu.Unlock() defer self.mu.Unlock()
if atomic.LoadInt32(&self.mining) == 1 { if atomic.LoadInt32(&self.mining) == 1 {
var keep []Agent
// stop all agents // stop all agents
for _, agent := range self.agents { for _, agent := range self.agents {
agent.Stop() agent.Stop()
// keep all that's not a cpu agent
if _, ok := agent.(*CpuAgent); !ok {
keep = append(keep, agent)
}
} }
self.agents = keep
} }
atomic.StoreInt32(&self.mining, 0) atomic.StoreInt32(&self.mining, 0)
@ -174,8 +193,11 @@ out:
self.possibleUncles[ev.Block.Hash()] = ev.Block self.possibleUncles[ev.Block.Hash()] = ev.Block
self.uncleMu.Unlock() self.uncleMu.Unlock()
case core.TxPreEvent: case core.TxPreEvent:
// Apply transaction to the pending state if we're not mining
if atomic.LoadInt32(&self.mining) == 0 { if atomic.LoadInt32(&self.mining) == 0 {
self.commitNewWork() self.mu.Lock()
self.commitTransactions(types.Transactions{ev.Tx})
self.mu.Unlock()
} }
} }
case <-self.quit: case <-self.quit:
@ -241,19 +263,33 @@ func (self *worker) makeCurrent() {
} }
block.Header().Extra = self.extra block.Header().Extra = self.extra
self.current = env(block, self.eth) current := env(block, self.eth)
for _, ancestor := range self.chain.GetAncestors(block, 7) { for _, ancestor := range self.chain.GetAncestors(block, 7) {
self.current.family.Add(ancestor.Hash()) current.family.Add(ancestor.Hash())
} }
accounts, _ := self.eth.AccountManager().Accounts()
// Keep track of transactions which return errors so they can be removed
current.remove = set.New()
current.tcount = 0
current.ignoredTransactors = set.New()
current.lowGasTransactors = set.New()
current.ownedAccounts = accountAddressesSet(accounts)
parent := self.chain.GetBlock(self.current.block.ParentHash()) parent := self.chain.GetBlock(current.block.ParentHash())
self.current.coinbase.SetGasPool(core.CalcGasLimit(parent)) current.coinbase.SetGasPool(core.CalcGasLimit(parent))
self.current = current
} }
func (w *worker) setGasPrice(p *big.Int) { func (w *worker) setGasPrice(p *big.Int) {
w.mu.Lock() w.mu.Lock()
defer w.mu.Unlock() defer w.mu.Unlock()
w.gasPrice = p
// calculate the minimal gas price the miner accepts when sorting out transactions.
const pct = int64(90)
w.gasPrice = gasprice(p, pct)
w.mux.Post(core.GasPriceChanged{w.gasPrice})
} }
func (self *worker) commitNewWork() { func (self *worker) commitNewWork() {
@ -265,68 +301,14 @@ func (self *worker) commitNewWork() {
defer self.currentMu.Unlock() defer self.currentMu.Unlock()
self.makeCurrent() self.makeCurrent()
current := self.current
transactions := self.eth.TxPool().GetTransactions() transactions := self.eth.TxPool().GetTransactions()
sort.Sort(types.TxByNonce{transactions}) sort.Sort(types.TxByNonce{transactions})
// Keep track of transactions which return errors so they can be removed // commit transactions for this run
var ( self.commitTransactions(transactions)
remove = set.New() self.eth.TxPool().RemoveTransactions(current.lowGasTxs)
tcount = 0
ignoredTransactors = set.New()
)
const pct = int64(90)
// calculate the minimal gas price the miner accepts when sorting out transactions.
minprice := gasprice(self.gasPrice, pct)
for _, tx := range transactions {
// We can skip err. It has already been validated in the tx pool
from, _ := tx.From()
// check if it falls within margin
if tx.GasPrice().Cmp(minprice) < 0 {
// ignore the transaction and transactor. We ignore the transactor
// because nonce will fail after ignoring this transaction so there's
// no point
ignoredTransactors.Add(from)
glog.V(logger.Info).Infof("transaction(%x) below gas price (<%d%% ask price). All sequential txs from this address(%x) will fail\n", tx.Hash().Bytes()[:4], pct, from[:4])
continue
}
// Move on to the next transaction when the transactor is in ignored transactions set
// This may occur when a transaction hits the gas limit. When a gas limit is hit and
// the transaction is processed (that could potentially be included in the block) it
// will throw a nonce error because the previous transaction hasn't been processed.
// Therefor we need to ignore any transaction after the ignored one.
if ignoredTransactors.Has(from) {
continue
}
self.current.state.StartRecord(tx.Hash(), common.Hash{}, 0)
err := self.commitTransaction(tx)
switch {
case core.IsNonceErr(err) || core.IsInvalidTxErr(err):
// Remove invalid transactions
from, _ := tx.From()
self.chain.TxState().RemoveNonce(from, tx.Nonce())
remove.Add(tx.Hash())
if glog.V(logger.Detail) {
glog.Infof("TX (%x) failed, will be removed: %v\n", tx.Hash().Bytes()[:4], err)
}
case state.IsGasLimitErr(err):
from, _ := tx.From()
// ignore the transactor so no nonce errors will be thrown for this account
// next time the worker is run, they'll be picked up again.
ignoredTransactors.Add(from)
glog.V(logger.Detail).Infof("Gas limit reached for (%x) in this block. Continue to try smaller txs\n", from[:4])
default:
tcount++
}
}
var ( var (
uncles []*types.Header uncles []*types.Header
@ -352,7 +334,7 @@ func (self *worker) commitNewWork() {
// We only care about logging if we're actually mining // We only care about logging if we're actually mining
if atomic.LoadInt32(&self.mining) == 1 { if atomic.LoadInt32(&self.mining) == 1 {
glog.V(logger.Info).Infof("commit new work on block %v with %d txs & %d uncles\n", self.current.block.Number(), tcount, len(uncles)) glog.V(logger.Info).Infof("commit new work on block %v with %d txs & %d uncles\n", current.block.Number(), current.tcount, len(uncles))
} }
for _, hash := range badUncles { for _, hash := range badUncles {
@ -392,6 +374,71 @@ func (self *worker) commitUncle(uncle *types.Header) error {
return nil return nil
} }
func (self *worker) commitTransactions(transactions types.Transactions) {
current := self.current
for _, tx := range transactions {
// We can skip err. It has already been validated in the tx pool
from, _ := tx.From()
// Check if it falls within margin. Txs from owned accounts are always processed.
if tx.GasPrice().Cmp(self.gasPrice) < 0 && !current.ownedAccounts.Has(from) {
// ignore the transaction and transactor. We ignore the transactor
// because nonce will fail after ignoring this transaction so there's
// no point
current.lowGasTransactors.Add(from)
glog.V(logger.Info).Infof("transaction(%x) below gas price (tx=%v ask=%v). All sequential txs from this address(%x) will be ignored\n", tx.Hash().Bytes()[:4], common.CurrencyToString(tx.GasPrice()), common.CurrencyToString(self.gasPrice), from[:4])
}
// Continue with the next transaction if the transaction sender is included in
// the low gas tx set. This will also remove the tx and all sequential transaction
// from this transactor
if current.lowGasTransactors.Has(from) {
// add tx to the low gas set. This will be removed at the end of the run
// owned accounts are ignored
if !current.ownedAccounts.Has(from) {
current.lowGasTxs = append(current.lowGasTxs, tx)
}
continue
}
// Move on to the next transaction when the transactor is in ignored transactions set
// This may occur when a transaction hits the gas limit. When a gas limit is hit and
// the transaction is processed (that could potentially be included in the block) it
// will throw a nonce error because the previous transaction hasn't been processed.
// Therefor we need to ignore any transaction after the ignored one.
if current.ignoredTransactors.Has(from) {
continue
}
self.current.state.StartRecord(tx.Hash(), common.Hash{}, 0)
err := self.commitTransaction(tx)
switch {
case core.IsNonceErr(err) || core.IsInvalidTxErr(err):
// Remove invalid transactions
from, _ := tx.From()
self.chain.TxState().RemoveNonce(from, tx.Nonce())
current.remove.Add(tx.Hash())
if glog.V(logger.Detail) {
glog.Infof("TX (%x) failed, will be removed: %v\n", tx.Hash().Bytes()[:4], err)
}
case state.IsGasLimitErr(err):
from, _ := tx.From()
// ignore the transactor so no nonce errors will be thrown for this account
// next time the worker is run, they'll be picked up again.
current.ignoredTransactors.Add(from)
glog.V(logger.Detail).Infof("Gas limit reached for (%x) in this block. Continue to try smaller txs\n", from[:4])
default:
current.tcount++
}
}
}
func (self *worker) commitTransaction(tx *types.Transaction) error { func (self *worker) commitTransaction(tx *types.Transaction) error {
snap := self.current.state.Copy() snap := self.current.state.Copy()
receipt, _, err := self.proc.ApplyTransaction(self.current.coinbase, self.current.state, self.current.block, tx, self.current.totalUsedGas, true) receipt, _, err := self.proc.ApplyTransaction(self.current.coinbase, self.current.state, self.current.block, tx, self.current.totalUsedGas, true)
@ -423,3 +470,11 @@ func gasprice(price *big.Int, pct int64) *big.Int {
p.Mul(p, big.NewInt(pct)) p.Mul(p, big.NewInt(pct))
return p return p
} }
func accountAddressesSet(accounts []accounts.Account) *set.Set {
accountSet := set.New()
for _, account := range accounts {
accountSet.Add(account.Address)
}
return accountSet
}

View File

@ -9,7 +9,7 @@ import (
logpkg "log" logpkg "log"
"net" "net"
"os" "os"
"path" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
"sync" "sync"
@ -88,7 +88,7 @@ func (test *udpTest) waitPacketOut(validate interface{}) error {
func (test *udpTest) errorf(format string, args ...interface{}) error { func (test *udpTest) errorf(format string, args ...interface{}) error {
_, file, line, ok := runtime.Caller(2) // errorf + waitPacketOut _, file, line, ok := runtime.Caller(2) // errorf + waitPacketOut
if ok { if ok {
file = path.Base(file) file = filepath.Base(file)
} else { } else {
file = "???" file = "???"
line = 1 line = 1

View File

@ -158,6 +158,17 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
v := api.xethAtStateNum(args.BlockNumber).CodeAtBytes(args.Address) v := api.xethAtStateNum(args.BlockNumber).CodeAtBytes(args.Address)
*reply = newHexData(v) *reply = newHexData(v)
case "eth_sign":
args := new(NewSigArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := api.xeth().Sign(args.From, args.Data, false)
if err != nil {
return err
}
*reply = v
case "eth_sendTransaction", "eth_transact": case "eth_sendTransaction", "eth_transact":
args := new(NewTxArgs) args := new(NewTxArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
@ -175,16 +186,24 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
return err return err
} }
*reply = v *reply = v
case "eth_call": case "eth_estimateGas":
args := new(CallArgs) _, gas, err := api.doCall(req.Params)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := api.xethAtStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
if err != nil { if err != nil {
return err return err
} }
// TODO unwrap the parent method's ToHex call
if len(gas) == 0 {
*reply = newHexNum(0)
} else {
*reply = newHexNum(gas)
}
case "eth_call":
v, _, err := api.doCall(req.Params)
if err != nil {
return err
}
// TODO unwrap the parent method's ToHex call // TODO unwrap the parent method's ToHex call
if v == "0x0" { if v == "0x0" {
*reply = newHexData([]byte{}) *reply = newHexData([]byte{})
@ -380,7 +399,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
} }
*reply = NewLogsRes(api.xeth().AllLogs(args.Earliest, args.Latest, args.Skip, args.Max, args.Address, args.Topics)) *reply = NewLogsRes(api.xeth().AllLogs(args.Earliest, args.Latest, args.Skip, args.Max, args.Address, args.Topics))
case "eth_getWork": case "eth_getWork":
api.xeth().SetMining(true) api.xeth().SetMining(true, 0)
*reply = api.xeth().RemoteMining().GetWork() *reply = api.xeth().RemoteMining().GetWork()
case "eth_submitWork": case "eth_submitWork":
args := new(SubmitWorkArgs) args := new(SubmitWorkArgs)
@ -439,10 +458,18 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
*reply = newHexData(res) *reply = newHexData(res)
case "shh_version": case "shh_version":
// Short circuit if whisper is not running
if api.xeth().Whisper() == nil {
return NewNotAvailableError(req.Method, "whisper offline")
}
// Retrieves the currently running whisper protocol version // Retrieves the currently running whisper protocol version
*reply = api.xeth().WhisperVersion() *reply = api.xeth().WhisperVersion()
case "shh_post": case "shh_post":
// Short circuit if whisper is not running
if api.xeth().Whisper() == nil {
return NewNotAvailableError(req.Method, "whisper offline")
}
// Injects a new message into the whisper network // Injects a new message into the whisper network
args := new(WhisperMessageArgs) args := new(WhisperMessageArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
@ -455,10 +482,18 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
*reply = true *reply = true
case "shh_newIdentity": case "shh_newIdentity":
// Short circuit if whisper is not running
if api.xeth().Whisper() == nil {
return NewNotAvailableError(req.Method, "whisper offline")
}
// Creates a new whisper identity to use for sending/receiving messages // Creates a new whisper identity to use for sending/receiving messages
*reply = api.xeth().Whisper().NewIdentity() *reply = api.xeth().Whisper().NewIdentity()
case "shh_hasIdentity": case "shh_hasIdentity":
// Short circuit if whisper is not running
if api.xeth().Whisper() == nil {
return NewNotAvailableError(req.Method, "whisper offline")
}
// Checks if an identity if owned or not // Checks if an identity if owned or not
args := new(WhisperIdentityArgs) args := new(WhisperIdentityArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
@ -467,6 +502,10 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
*reply = api.xeth().Whisper().HasIdentity(args.Identity) *reply = api.xeth().Whisper().HasIdentity(args.Identity)
case "shh_newFilter": case "shh_newFilter":
// Short circuit if whisper is not running
if api.xeth().Whisper() == nil {
return NewNotAvailableError(req.Method, "whisper offline")
}
// Create a new filter to watch and match messages with // Create a new filter to watch and match messages with
args := new(WhisperFilterArgs) args := new(WhisperFilterArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
@ -476,6 +515,10 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
*reply = newHexNum(big.NewInt(int64(id)).Bytes()) *reply = newHexNum(big.NewInt(int64(id)).Bytes())
case "shh_uninstallFilter": case "shh_uninstallFilter":
// Short circuit if whisper is not running
if api.xeth().Whisper() == nil {
return NewNotAvailableError(req.Method, "whisper offline")
}
// Remove an existing filter watching messages // Remove an existing filter watching messages
args := new(FilterIdArgs) args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
@ -484,6 +527,10 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
*reply = api.xeth().UninstallWhisperFilter(args.Id) *reply = api.xeth().UninstallWhisperFilter(args.Id)
case "shh_getFilterChanges": case "shh_getFilterChanges":
// Short circuit if whisper is not running
if api.xeth().Whisper() == nil {
return NewNotAvailableError(req.Method, "whisper offline")
}
// Retrieve all the new messages arrived since the last request // Retrieve all the new messages arrived since the last request
args := new(FilterIdArgs) args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
@ -492,12 +539,17 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
*reply = api.xeth().WhisperMessagesChanged(args.Id) *reply = api.xeth().WhisperMessagesChanged(args.Id)
case "shh_getMessages": case "shh_getMessages":
// Short circuit if whisper is not running
if api.xeth().Whisper() == nil {
return NewNotAvailableError(req.Method, "whisper offline")
}
// Retrieve all the cached messages matching a specific, existing filter // Retrieve all the cached messages matching a specific, existing filter
args := new(FilterIdArgs) args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
return err return err
} }
*reply = api.xeth().WhisperMessages(args.Id) *reply = api.xeth().WhisperMessages(args.Id)
case "eth_hashrate": case "eth_hashrate":
*reply = newHexNum(api.xeth().HashRate()) *reply = newHexNum(api.xeth().HashRate())
@ -527,3 +579,12 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
glog.V(logger.Detail).Infof("Reply: %T %s\n", reply, reply) glog.V(logger.Detail).Infof("Reply: %T %s\n", reply, reply)
return nil return nil
} }
func (api *EthereumApi) doCall(params json.RawMessage) (string, string, error) {
args := new(CallArgs)
if err := json.Unmarshal(params, &args); err != nil {
return "", "", err
}
return api.xethAtStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
}

View File

@ -31,6 +31,7 @@ func TestWeb3Sha3(t *testing.T) {
} }
func TestCompileSolidity(t *testing.T) { func TestCompileSolidity(t *testing.T) {
t.Skip()
solc, err := compiler.New("") solc, err := compiler.New("")
if solc == nil { if solc == nil {
@ -45,7 +46,7 @@ func TestCompileSolidity(t *testing.T) {
jsonstr := `{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["` + source + `"],"id":64}` jsonstr := `{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["` + source + `"],"id":64}`
expCode := "605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056" //expCode := "605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056"
expAbiDefinition := `[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]` expAbiDefinition := `[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]`
expUserDoc := `{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}}` expUserDoc := `{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}}`
expDeveloperDoc := `{"methods":{}}` expDeveloperDoc := `{"methods":{}}`
@ -75,9 +76,11 @@ func TestCompileSolidity(t *testing.T) {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
if contract.Code != expCode { /*
t.Errorf("Expected %s got %s", expCode, contract.Code) if contract.Code != expCode {
} t.Errorf("Expected %s got %s", expCode, contract.Code)
}
*/
if strconv.Quote(contract.Info.Source) != `"`+expSource+`"` { if strconv.Quote(contract.Info.Source) != `"`+expSource+`"` {
t.Errorf("Expected \n'%s' got \n'%s'", expSource, strconv.Quote(contract.Info.Source)) t.Errorf("Expected \n'%s' got \n'%s'", expSource, strconv.Quote(contract.Info.Source))
} }

View File

@ -166,6 +166,46 @@ type NewTxArgs struct {
BlockNumber int64 BlockNumber int64
} }
type NewSigArgs struct {
From string
Data string
}
func (args *NewSigArgs) UnmarshalJSON(b []byte) (err error) {
var obj []json.RawMessage
var ext struct {
From string
Data string
}
// Decode byte slice to array of RawMessages
if err := json.Unmarshal(b, &obj); err != nil {
return NewDecodeParamError(err.Error())
}
// Check for sufficient params
if len(obj) < 1 {
return NewInsufficientParamsError(len(obj), 1)
}
// Decode 0th RawMessage to temporary struct
if err := json.Unmarshal(obj[0], &ext); err != nil {
return NewDecodeParamError(err.Error())
}
if len(ext.From) == 0 {
return NewValidationError("from", "is required")
}
if len(ext.Data) == 0 {
return NewValidationError("data", "is required")
}
args.From = ext.From
args.Data = ext.Data
return nil
}
func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) { func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) {
var obj []json.RawMessage var obj []json.RawMessage
var ext struct { var ext struct {

View File

@ -116,7 +116,7 @@ func RpcResponse(api *EthereumApi, request *RpcRequest) *interface{} {
switch reserr.(type) { switch reserr.(type) {
case nil: case nil:
response = &RpcSuccessResponse{Jsonrpc: jsonrpcver, Id: request.Id, Result: reply} response = &RpcSuccessResponse{Jsonrpc: jsonrpcver, Id: request.Id, Result: reply}
case *NotImplementedError: case *NotImplementedError, *NotAvailableError:
jsonerr := &RpcErrorObject{-32601, reserr.Error()} jsonerr := &RpcErrorObject{-32601, reserr.Error()}
response = &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: request.Id, Error: jsonerr} response = &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: request.Id, Error: jsonerr}
case *DecodeParamError, *InsufficientParamsError, *ValidationError, *InvalidTypeError: case *DecodeParamError, *InsufficientParamsError, *ValidationError, *InvalidTypeError:

View File

@ -2,7 +2,7 @@ package rpc
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/jsre" "github.com/ethereum/go-ethereum/jsre"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
) )
@ -35,7 +35,6 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
} }
jsonreq, err := json.Marshal(reqif) jsonreq, err := json.Marshal(reqif)
var reqs []RpcRequest var reqs []RpcRequest
batch := true batch := true
err = json.Unmarshal(jsonreq, &reqs) err = json.Unmarshal(jsonreq, &reqs)
@ -52,6 +51,7 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
var respif interface{} var respif interface{}
err = self.ethApi.GetRequestReply(&req, &respif) err = self.ethApi.GetRequestReply(&req, &respif)
if err != nil { if err != nil {
fmt.Println("Error response:", err)
return self.err(call, -32603, err.Error(), req.Id) return self.err(call, -32603, err.Error(), req.Id)
} }
call.Otto.Set("ret_jsonrpc", jsonrpcver) call.Otto.Set("ret_jsonrpc", jsonrpcver)

View File

@ -18,6 +18,7 @@ package rpc
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big" "math/big"
@ -117,7 +118,13 @@ func newHexData(input interface{}) *hexdata {
binary.BigEndian.PutUint32(buff, input) binary.BigEndian.PutUint32(buff, input)
d.data = buff d.data = buff
case string: // hexstring case string: // hexstring
d.data = common.Big(input).Bytes() // aaargh ffs TODO: avoid back-and-forth hex encodings where unneeded
bytes, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
if err != nil {
d.isNil = true
} else {
d.data = bytes
}
default: default:
d.isNil = true d.isNil = true
} }
@ -209,6 +216,22 @@ func NewNotImplementedError(method string) *NotImplementedError {
} }
} }
type NotAvailableError struct {
Method string
Reason string
}
func (e *NotAvailableError) Error() string {
return fmt.Sprintf("%s method not available: %s", e.Method, e.Reason)
}
func NewNotAvailableError(method string, reason string) *NotAvailableError {
return &NotAvailableError{
Method: method,
Reason: reason,
}
}
type DecodeParamError struct { type DecodeParamError struct {
err string err string
} }

View File

@ -1,7 +1,7 @@
package tests package tests
import ( import (
"path" "path/filepath"
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
@ -99,7 +99,7 @@ func runBlockTest(name string, test *BlockTest, t *testing.T) {
} }
func testEthConfig() *eth.Config { func testEthConfig() *eth.Config {
ks := crypto.NewKeyStorePassphrase(path.Join(common.DefaultDataDir(), "keys")) ks := crypto.NewKeyStorePassphrase(filepath.Join(common.DefaultDataDir(), "keystore"))
return &eth.Config{ return &eth.Config{
DataDir: common.DefaultDataDir(), DataDir: common.DefaultDataDir(),

View File

@ -113,7 +113,7 @@ func (t *BlockTest) InsertPreState(ethereum *eth.Ethereum) (*state.StateDB, erro
if acct.PrivateKey != "" { if acct.PrivateKey != "" {
privkey, err := hex.DecodeString(strings.TrimPrefix(acct.PrivateKey, "0x")) privkey, err := hex.DecodeString(strings.TrimPrefix(acct.PrivateKey, "0x"))
err = crypto.ImportBlockTestKey(privkey) err = crypto.ImportBlockTestKey(privkey)
err = ethereum.AccountManager().TimedUnlock(addr, "", 999999*time.Second) err = ethereum.AccountManager().TimedUnlock(common.BytesToAddress(addr), "", 999999*time.Second)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -26,7 +26,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"sort" "sort"
@ -144,7 +144,7 @@ func getFiles(out chan<- string) {
return return
} }
} }
ext := path.Ext(line) ext := filepath.Ext(line)
for _, wantExt := range extensions { for _, wantExt := range extensions {
if ext == wantExt { if ext == wantExt {
goto send goto send

View File

@ -79,7 +79,6 @@ func New(eth *eth.Ethereum, frontend Frontend) *XEth {
xeth := &XEth{ xeth := &XEth{
backend: eth, backend: eth,
frontend: frontend, frontend: frontend,
whisper: NewWhisper(eth.Whisper()),
quit: make(chan struct{}), quit: make(chan struct{}),
filterManager: filter.NewFilterManager(eth.EventMux()), filterManager: filter.NewFilterManager(eth.EventMux()),
logQueue: make(map[int]*logQueue), logQueue: make(map[int]*logQueue),
@ -88,6 +87,9 @@ func New(eth *eth.Ethereum, frontend Frontend) *XEth {
messages: make(map[int]*whisperFilter), messages: make(map[int]*whisperFilter),
agent: miner.NewRemoteAgent(), agent: miner.NewRemoteAgent(),
} }
if eth.Whisper() != nil {
xeth.whisper = NewWhisper(eth.Whisper())
}
eth.Miner().Register(xeth.agent) eth.Miner().Register(xeth.agent)
if frontend == nil { if frontend == nil {
xeth.frontend = dummyFrontend{} xeth.frontend = dummyFrontend{}
@ -363,7 +365,7 @@ func (self *XEth) Accounts() []string {
accounts, _ := self.backend.AccountManager().Accounts() accounts, _ := self.backend.AccountManager().Accounts()
accountAddresses := make([]string, len(accounts)) accountAddresses := make([]string, len(accounts))
for i, ac := range accounts { for i, ac := range accounts {
accountAddresses[i] = common.ToHex(ac.Address) accountAddresses[i] = ac.Address.Hex()
} }
return accountAddresses return accountAddresses
} }
@ -423,10 +425,10 @@ func (self *XEth) ClientVersion() string {
return self.backend.ClientVersion() return self.backend.ClientVersion()
} }
func (self *XEth) SetMining(shouldmine bool) bool { func (self *XEth) SetMining(shouldmine bool, threads int) bool {
ismining := self.backend.IsMining() ismining := self.backend.IsMining()
if shouldmine && !ismining { if shouldmine && !ismining {
err := self.backend.StartMining() err := self.backend.StartMining(threads)
return err == nil return err == nil
} }
if ismining && !shouldmine { if ismining && !shouldmine {
@ -771,7 +773,7 @@ func (self *XEth) PushTx(encodedTx string) (string, error) {
return tx.Hash().Hex(), nil return tx.Hash().Hex(), nil
} }
func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, string, error) {
statedb := self.State().State() //self.eth.ChainManager().TransState() statedb := self.State().State() //self.eth.ChainManager().TransState()
var from *state.StateObject var from *state.StateObject
if len(fromStr) == 0 { if len(fromStr) == 0 {
@ -779,12 +781,13 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st
if err != nil || len(accounts) == 0 { if err != nil || len(accounts) == 0 {
from = statedb.GetOrNewStateObject(common.Address{}) from = statedb.GetOrNewStateObject(common.Address{})
} else { } else {
from = statedb.GetOrNewStateObject(common.BytesToAddress(accounts[0].Address)) from = statedb.GetOrNewStateObject(accounts[0].Address)
} }
} else { } else {
from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr)) from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr))
} }
from.SetGasPool(self.backend.ChainManager().GasLimit())
msg := callmsg{ msg := callmsg{
from: from, from: from,
to: common.HexToAddress(toStr), to: common.HexToAddress(toStr),
@ -805,14 +808,43 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st
block := self.CurrentBlock() block := self.CurrentBlock()
vmenv := core.NewEnv(statedb, self.backend.ChainManager(), msg, block) vmenv := core.NewEnv(statedb, self.backend.ChainManager(), msg, block)
res, err := vmenv.Call(msg.from, msg.to, msg.data, msg.gas, msg.gasPrice, msg.value) res, gas, err := core.ApplyMessage(vmenv, msg, from)
return common.ToHex(res), err return common.ToHex(res), gas.String(), err
} }
func (self *XEth) ConfirmTransaction(tx string) bool { func (self *XEth) ConfirmTransaction(tx string) bool {
return self.frontend.ConfirmTransaction(tx) return self.frontend.ConfirmTransaction(tx)
} }
func (self *XEth) doSign(from common.Address, hash common.Hash, didUnlock bool) ([]byte, error) {
sig, err := self.backend.AccountManager().Sign(accounts.Account{Address: from}, hash.Bytes())
if err == accounts.ErrLocked {
if didUnlock {
return nil, fmt.Errorf("signer account still locked after successful unlock")
}
if !self.frontend.UnlockAccount(from.Bytes()) {
return nil, fmt.Errorf("could not unlock signer account")
}
// retry signing, the account should now be unlocked.
return self.doSign(from, hash, true)
} else if err != nil {
return nil, err
}
return sig, nil
}
func (self *XEth) Sign(fromStr, hashStr string, didUnlock bool) (string, error) {
var (
from = common.HexToAddress(fromStr)
hash = common.HexToHash(hashStr)
)
sig, err := self.doSign(from, hash, didUnlock)
if err != nil {
return "", err
}
return common.ToHex(sig), nil
}
func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) {
// this minimalistic recoding is enough (works for natspec.js) // this minimalistic recoding is enough (works for natspec.js)
@ -905,17 +937,9 @@ func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceS
} }
func (self *XEth) sign(tx *types.Transaction, from common.Address, didUnlock bool) error { func (self *XEth) sign(tx *types.Transaction, from common.Address, didUnlock bool) error {
sig, err := self.backend.AccountManager().Sign(accounts.Account{Address: from.Bytes()}, tx.Hash().Bytes()) hash := tx.Hash()
if err == accounts.ErrLocked { sig, err := self.doSign(from, hash, didUnlock)
if didUnlock { if err != nil {
return fmt.Errorf("sender account still locked after successful unlock")
}
if !self.frontend.UnlockAccount(from.Bytes()) {
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 return err
} }
tx.SetSignatureValues(sig) tx.SetSignatureValues(sig)