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
import (
"bytes"
"crypto/ecdsa"
crand "crypto/rand"
"errors"
@ -41,6 +40,7 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
@ -50,12 +50,12 @@ var (
)
type Account struct {
Address []byte
Address common.Address
}
type Manager struct {
keyStore crypto.KeyStore2
unlocked map[string]*unlocked
unlocked map[common.Address]*unlocked
mutex sync.RWMutex
}
@ -67,40 +67,40 @@ type unlocked struct {
func NewManager(keyStore crypto.KeyStore2) *Manager {
return &Manager{
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()
for _, acct := range accounts {
if bytes.Compare(acct.Address, addr) == 0 {
if acct.Address == addr {
return true
}
}
return false
}
func (am *Manager) Primary() (addr []byte, err error) {
func (am *Manager) Primary() (addr common.Address, err error) {
addrs, err := am.keyStore.GetKeyAddresses()
if os.IsNotExist(err) {
return nil, ErrNoKeys
return common.Address{}, ErrNoKeys
} else if err != nil {
return nil, err
return common.Address{}, err
}
if len(addrs) == 0 {
return nil, ErrNoKeys
return common.Address{}, ErrNoKeys
}
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)
}
func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
am.mutex.RLock()
unlockedKey, found := am.unlocked[string(a.Address)]
unlockedKey, found := am.unlocked[a.Address]
am.mutex.RUnlock()
if !found {
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.
// 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)
if err != nil {
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
// stays unlocked until the program exits or until a TimedUnlock
// timeout (started after the call to Unlock) expires.
func (am *Manager) Unlock(addr []byte, keyAuth string) error {
func (am *Manager) Unlock(addr common.Address, keyAuth string) error {
key, err := am.keyStore.GetKey(addr, keyAuth)
if err != nil {
return err
@ -157,10 +157,10 @@ func (am *Manager) Accounts() ([]Account, error) {
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{})}
am.mutex.Lock()
prev, found := am.unlocked[string(addr)]
prev, found := am.unlocked[addr]
if found {
// terminate dropLater for this key to avoid unexpected drops.
close(prev.abort)
@ -169,12 +169,12 @@ func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
// key, i.e. when Unlock was used.
zeroKey(prev.PrivateKey)
}
am.unlocked[string(addr)] = u
am.unlocked[addr] = u
am.mutex.Unlock()
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)
defer t.Stop()
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
// because the map stores a new pointer every time the key is
// unlocked.
if am.unlocked[string(addr)] == u {
if am.unlocked[addr] == u {
zeroKey(u.PrivateKey)
delete(am.unlocked, string(addr))
delete(am.unlocked, addr)
}
am.mutex.Unlock()
}
@ -204,7 +204,7 @@ func zeroKey(k *ecdsa.PrivateKey) {
// USE WITH CAUTION = this will save an unencrypted private key on disk
// 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)
if err != nil {
return err

View File

@ -126,7 +126,7 @@ func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
// Add the accouns to a new set
accountSet := set.New()
for _, account := range accounts {
accountSet.Add(common.BytesToAddress(account.Address))
accountSet.Add(account.Address)
}
//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 {
_, err := call.Argument(0).ToInteger()
var (
threads int64
err error
)
if len(call.ArgumentList) > 0 {
threads, err = call.Argument(0).ToInteger()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
// threads now ignored
} else {
threads = 4
}
err = js.ethereum.StartMining()
err = js.ethereum.StartMining(int(threads))
if err != nil {
fmt.Println(err)
return otto.FalseValue()
@ -383,7 +391,7 @@ func (js *jsre) unlock(call otto.FunctionCall) otto.Value {
}
}
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 {
fmt.Printf("Unlock account failed '%v'\n", err)
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)
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 {

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"
"math/big"
"os"
"path"
"path/filepath"
"strings"
"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/natspec"
"github.com/ethereum/go-ethereum/eth"
@ -164,7 +165,7 @@ func (self *jsre) UnlockAccount(addr []byte) bool {
return false
}
// 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
} else {
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)) {
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 {
fmt.Printf("unable to open history file: %v\n", err)
return

View File

@ -4,7 +4,6 @@ import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
@ -25,10 +24,13 @@ import (
const (
testSolcPath = ""
solcVersion = "0.9.17"
testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674"
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
testBalance = "10000000000000000000"
// of empty string
testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
)
var (
@ -43,7 +45,7 @@ type testjethre struct {
}
func (self *testjethre) UnlockAccount(acc []byte) bool {
err := self.ethereum.AccountManager().Unlock(acc, "")
err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
if err != nil {
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
core.GenesisData = []byte(testGenesis)
ks := crypto.NewKeyStorePassphrase(filepath.Join(tmp, "keys"))
ks := crypto.NewKeyStorePassphrase(filepath.Join(tmp, "keystore"))
am := accounts.NewManager(ks)
ethereum, err := eth.New(&eth.Config{
DataDir: tmp,
@ -93,7 +95,7 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
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("/")
if err != nil {
t.Errorf("Error creating DocServer: %v", err)
@ -215,7 +217,34 @@ func TestCheckTestAccountBalance(t *testing.T) {
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) {
t.Skip()
tmp, repl, ethereum := testJEthRE(t)
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, `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 {
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")
if err != nil {
t.Fatalf("%v", err)
@ -259,6 +295,7 @@ func TestContract(t *testing.T) {
} else {
checkEvalJSON(t, repl, `contract = eth.compile.solidity(source)`, string(contractInfo))
}
checkEvalJSON(t, repl, `contract.code`, `"605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056"`)
checkEvalJSON(
@ -298,7 +335,7 @@ multiply7 = new Multiply7(contractaddress);
}
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`)
if err != nil {
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 {
_, file, line, _ := runtime.Caller(1)
file = path.Base(file)
file = filepath.Base(file)
fmt.Printf("\t%s:%d: %v\n", file, line, err)
t.Fail()
}

View File

@ -26,7 +26,6 @@ import (
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
@ -51,7 +50,7 @@ import _ "net/http/pprof"
const (
ClientIdentifier = "Geth"
Version = "0.9.19"
Version = "0.9.20"
)
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
// Attempt to unlock the account
passphrase = getPassPhrase(ctx, "", false)
accbytes := common.FromHex(account)
if len(accbytes) == 0 {
if len(account) == 0 {
utils.Fatalf("Invalid account address '%s'", account)
}
err = am.Unlock(accbytes, passphrase)
err = am.Unlock(common.StringToAddress(account), passphrase)
if err != nil {
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)
if len(account) > 0 {
if account == "primary" {
accbytes, err := am.Primary()
primaryAcc, err := am.Primary()
if err != nil {
utils.Fatalf("no primary account: %v", err)
}
account = common.ToHex(accbytes)
account = primaryAcc.Hex()
}
unlockAccount(ctx, am, account)
}
@ -401,7 +399,7 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
}
}
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)
}
}
@ -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"))
exportFile := path.Join(ctx.GlobalString(utils.DataDirFlag.Name), filename)
exportFile := filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), filename)
err = utils.ExportChain(ethereum.ChainManager(), exportFile)
if err != nil {
@ -576,7 +574,7 @@ func upgradeDb(ctx *cli.Context) {
ethereum.StateDb().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)
if err != nil {

View File

@ -27,7 +27,7 @@ import (
"fmt"
"io/ioutil"
"math/big"
"path"
"path/filepath"
"runtime"
"sort"
"time"
@ -79,7 +79,7 @@ type Gui struct {
// Create GUI, but doesn't start it
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 {
panic(err)
}
@ -92,7 +92,7 @@ func NewWindow(ethereum *eth.Ethereum) *Gui {
plugins: make(map[string]plugin),
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)
return gui
@ -232,7 +232,7 @@ func (self *Gui) loadMergedMiningOptions() {
func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
var inout string
from, _ := tx.From()
if gui.eth.AccountManager().HasAccount(common.Hex2Bytes(from.Hex())) {
if gui.eth.AccountManager().HasAccount(from) {
inout = "send"
} else {
inout = "recv"

View File

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

View File

@ -22,7 +22,7 @@ package main
import (
"io/ioutil"
"path"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
@ -110,7 +110,7 @@ func (ui *UiLib) ConnectToPeer(nodeURL 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) {
@ -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)
return self.XEth.Call(
@ -159,7 +159,7 @@ func (self *UiLib) RemoveLocalTransaction(id int) {
func (self *UiLib) ToggleMining() bool {
if !self.eth.IsMining() {
err := self.eth.StartMining()
err := self.eth.StartMining(4)
return err == nil
} else {
self.eth.StopMining()
@ -218,7 +218,7 @@ func (self *UiLib) Messages(id int) *common.List {
}
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 {
guilogger.Infoln("error reading file", p, ":", err)
}

View File

@ -7,7 +7,7 @@ import (
"math/big"
"net/http"
"os"
"path"
"path/filepath"
"runtime"
"github.com/codegangsta/cli"
@ -55,7 +55,7 @@ OPTIONS:
// NewApp creates an app with sane defaults.
func NewApp(version, usage string) *cli.App {
app := cli.NewApp()
app.Name = path.Base(os.Args[0])
app.Name = filepath.Base(os.Args[0])
app.Author = ""
//app.Authors = nil
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) {
dataDir := ctx.GlobalString(DataDirFlag.Name)
blockDb, err := ethdb.NewLDBDatabase(path.Join(dataDir, "blockchain"))
blockDb, err := ethdb.NewLDBDatabase(filepath.Join(dataDir, "blockchain"))
if err != nil {
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 {
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 {
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 {
dataDir := ctx.GlobalString(DataDirFlag.Name)
ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys"))
ks := crypto.NewKeyStorePassphrase(filepath.Join(dataDir, "keystore"))
return accounts.NewManager(ks)
}

View File

@ -7,7 +7,6 @@ import (
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"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)
}
func (sol *Solidity) Version() string {
return sol.version
}
func (sol *Solidity) Compile(source string) (contract *Contract, err error) {
if len(source) == 0 {
@ -126,10 +129,10 @@ func (sol *Solidity) Compile(source string) (contract *Contract, err error) {
_, file := filepath.Split(matches[0])
base := strings.Split(file, ".")[0]
codeFile := path.Join(wd, base+".binary")
abiDefinitionFile := path.Join(wd, base+".abi")
userDocFile := path.Join(wd, base+".docuser")
developerDocFile := path.Join(wd, base+".docdev")
codeFile := filepath.Join(wd, base+".binary")
abiDefinitionFile := filepath.Join(wd, base+".abi")
userDocFile := filepath.Join(wd, base+".docuser")
developerDocFile := filepath.Join(wd, base+".docdev")
code, err := ioutil.ReadFile(codeFile)
if err != nil {

View File

@ -9,6 +9,8 @@ import (
"github.com/ethereum/go-ethereum/common"
)
const solcVersion = "0.9.17"
var (
source = `
contract test {
@ -19,9 +21,9 @@ contract test {
}
`
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) {
@ -34,14 +36,16 @@ func TestCompiler(t *testing.T) {
t.Errorf("error compiling source. result %v: %v", contract, err)
return
}
/*
if contract.Code != code {
t.Errorf("wrong code, expected\n%s, got\n%s", code, contract.Code)
}
*/
}
func TestCompileError(t *testing.T) {
sol, err := New("")
if err != nil {
if err != nil || sol.version != solcVersion {
t.Skip("no solc installed")
}
contract, err := sol.Compile(source[2:])

View File

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

View File

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

View File

@ -25,8 +25,6 @@ func (s *SizeSuite) TestStorageSizeString(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))
finney := CurrencyToString(BigPow(10, 16))
szabo := CurrencyToString(BigPow(10, 13))
@ -35,8 +33,6 @@ func (s *CommonSuite) TestCommon(c *checker.C) {
ada := CurrencyToString(BigPow(10, 4))
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(finney, checker.Equals, "10 Finney")
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(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"
"math/big"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"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) {
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 {
return nil, err
}

View File

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

View File

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

View File

@ -21,7 +21,7 @@ var (
ErrInvalidSender = errors.New("Invalid sender")
ErrNonce = errors.New("Nonce too low")
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")
ErrIntrinsicGas = errors.New("Intrinsic gas too low")
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.
func ImportBlockTestKey(privKeyBytes []byte) error {
ks := NewKeyStorePassphrase(common.DefaultDataDir() + "/keys")
ks := NewKeyStorePassphrase(common.DefaultDataDir() + "/keystore")
ecKey := ToECDSA(privKeyBytes)
key := &Key{
Id: uuid.NewRandom(),
Address: PubkeyToAddress(ecKey.PublicKey),
Address: common.BytesToAddress(PubkeyToAddress(ecKey.PublicKey)),
PrivateKey: ecKey,
}
err := ks.StoreKey(key, "")
@ -231,13 +231,13 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
ecKey := ToECDSA(ethPriv)
key = &Key{
Id: nil,
Address: PubkeyToAddress(ecKey.PublicKey),
Address: common.BytesToAddress(PubkeyToAddress(ecKey.PublicKey)),
PrivateKey: ecKey,
}
derivedAddr := common.Bytes2Hex(key.Address)
derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
expectedAddr := preSaleKeyStruct.EthAddr
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
}
@ -252,7 +252,7 @@ func aesCBCDecrypt(key []byte, cipherText []byte, iv []byte) (plainText []byte,
decrypter.CryptBlocks(paddedPlainText, cipherText)
plainText = PKCS7Unpad(paddedPlainText)
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
}

View File

@ -26,44 +26,69 @@ package crypto
import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"io"
"code.google.com/p/go-uuid/uuid"
"github.com/ethereum/go-ethereum/common"
)
const (
version = "1"
)
type Key struct {
Id uuid.UUID // Version 4 "random" for unique id not derived from key data
// 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
// privkey in this struct is always in plaintext
PrivateKey *ecdsa.PrivateKey
}
type plainKeyJSON struct {
Id []byte
Address []byte
PrivateKey []byte
}
type cipherJSON struct {
Salt []byte
IV []byte
CipherText []byte
Address string `json:"address"`
PrivateKey string `json:"privatekey"`
Id string `json:"id"`
Version string `json:"version"`
}
type encryptedKeyJSON struct {
Id []byte
Address []byte
Crypto cipherJSON
Address string `json:"address"`
Crypto cryptoJSON
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) {
jStruct := plainKeyJSON{
k.Id,
k.Address,
FromECDSA(k.PrivateKey),
hex.EncodeToString(k.Address[:]),
hex.EncodeToString(FromECDSA(k.PrivateKey)),
k.Id.String(),
version,
}
j, err = json.Marshal(jStruct)
return j, err
@ -77,19 +102,29 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) {
}
u := new(uuid.UUID)
*u = keyJSON.Id
*u = uuid.Parse(keyJSON.Id)
k.Id = *u
k.Address = keyJSON.Address
k.PrivateKey = ToECDSA(keyJSON.PrivateKey)
addr, err := hex.DecodeString(keyJSON.Address)
if err != nil {
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 {
id := uuid.NewRandom()
key := &Key{
Id: id,
Address: PubkeyToAddress(privateKeyECDSA.PublicKey),
Address: common.BytesToAddress(PubkeyToAddress(privateKeyECDSA.PublicKey)),
PrivateKey: privateKeyECDSA,
}
return key

View File

@ -28,24 +28,25 @@ the private key is encrypted and on disk uses another JSON encoding.
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.
2. Scrypt salt is 32 random bytes from CSPRNG. It is appended to ciphertext.
3. Checksum is SHA3 of the private key bytes.
4. Plaintext is concatenation of private key bytes and checksum.
5. Encryption algo is AES 256 CBC [3][4]
6. CBC IV is 16 random bytes from CSPRNG. It is appended to ciphertext.
2. Scrypt salt is 32 random bytes from CSPRNG.
It's stored in plain next to ciphertext in key file.
3. MAC is SHA3-256 of concatenation of ciphertext and last 16 bytes of scrypt derived key.
4. Plaintext is the EC private key bytes.
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]
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.
2. byte arrays are base64 JSON strings.
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].
4. The checksum is the last 32 bytes of the plaintext byte array and the
private key is the preceeding bytes.
References:
@ -72,14 +73,17 @@ import (
"errors"
"io"
"os"
"path"
"path/filepath"
"code.google.com/p/go-uuid/uuid"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/randentropy"
"golang.org/x/crypto/scrypt"
)
const (
keyHeaderVersion = "1"
keyHeaderKDF = "scrypt"
// 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
scryptN = 1 << 18
scryptr = 8
@ -99,7 +103,7 @@ func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *K
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)
if err != nil {
return nil, err
@ -112,43 +116,63 @@ func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *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)
}
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
authArray := []byte(auth)
salt := randentropy.GetEntropyMixed(32)
salt := randentropy.GetEntropyCSPRNG(32)
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
if err != nil {
return err
}
keyBytes := FromECDSA(key.PrivateKey)
keyBytesHash := Sha3(keyBytes)
toEncrypt := PKCS7Pad(append(keyBytes, keyBytesHash...))
encryptKey := Sha3(derivedKey[:16])[:16]
AES256Block, err := aes.NewCipher(derivedKey)
keyBytes := FromECDSA(key.PrivateKey)
toEncrypt := PKCS7Pad(keyBytes)
AES128Block, err := aes.NewCipher(encryptKey)
if err != nil {
return err
}
iv := randentropy.GetEntropyMixed(aes.BlockSize) // 16
AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv)
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
AES128CBCEncrypter := cipher.NewCBCEncrypter(AES128Block, iv)
cipherText := make([]byte, len(toEncrypt))
AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt)
AES128CBCEncrypter.CryptBlocks(cipherText, toEncrypt)
cipherStruct := cipherJSON{
salt,
iv,
cipherText,
mac := Sha3(derivedKey[16:32], cipherText)
scryptParamsJSON := scryptParamsJSON{
N: scryptN,
R: scryptr,
P: scryptp,
DkLen: scryptdkLen,
Salt: hex.EncodeToString(salt),
}
keyStruct := encryptedKeyJSON{
key.Id,
key.Address,
cipherStruct,
cipherParamsJSON := cipherparamsJSON{
IV: hex.EncodeToString(iv),
}
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 {
return err
}
@ -156,18 +180,18 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
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
_, _, err = DecryptKey(ks, keyAddr, auth)
if err != nil {
return err
}
keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr))
keyDirPath := filepath.Join(ks.keysDirPath, hex.EncodeToString(keyAddr[:]))
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)
if err != nil {
return nil, nil, err
@ -176,25 +200,48 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []
keyProtected := new(encryptedKeyJSON)
err = json.Unmarshal(fileContent, keyProtected)
keyId = keyProtected.Id
salt := keyProtected.Crypto.Salt
iv := keyProtected.Crypto.IV
cipherText := keyProtected.Crypto.CipherText
keyId = uuid.Parse(keyProtected.Id)
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
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)
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
derivedKey, err := scrypt.Key(authArray, salt, n, r, p, dkLen)
if err != nil {
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 {
return nil, nil, err
}
keyBytes = plainText[:len(plainText)-32]
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
return plainText, keyId, err
}

View File

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

View File

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

View File

@ -2,12 +2,8 @@ package randentropy
import (
crand "crypto/rand"
"encoding/binary"
"github.com/ethereum/go-ethereum/crypto/sha3"
"io"
"os"
"strings"
"time"
)
var Reader io.Reader = &randEntropy{}
@ -16,7 +12,7 @@ type randEntropy struct {
}
func (*randEntropy) Read(bytes []byte) (n int, err error) {
readBytes := GetEntropyMixed(len(bytes))
readBytes := GetEntropyCSPRNG(len(bytes))
copy(bytes, readBytes)
return len(bytes), nil
}
@ -29,40 +25,6 @@ func Sha3(data []byte) []byte {
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 {
mainBuff := make([]byte, n)
_, err := io.ReadFull(crand.Reader, mainBuff)
@ -71,14 +33,3 @@ func GetEntropyCSPRNG(n int) []byte {
}
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
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 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) {
nonce := randentropy.GetEntropyMixed(32)
nonce := randentropy.GetEntropyCSPRNG(32)
var sig []byte = make([]byte, 65)
var recid C.int

View File

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

View File

@ -7,7 +7,6 @@ import (
"io/ioutil"
"math/big"
"os"
"path"
"path/filepath"
"strings"
"time"
@ -145,7 +144,7 @@ func (cfg *Config) nodeKey() (*ecdsa.PrivateKey, error) {
return cfg.NodeKey, nil
}
// use persistent key if present
keyfile := path.Join(cfg.DataDir, "nodekey")
keyfile := filepath.Join(cfg.DataDir, "nodekey")
key, err := crypto.LoadECDSA(keyfile)
if err == nil {
return key, nil
@ -207,29 +206,33 @@ func New(config *Config) (*Ethereum, error) {
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
if newdb == nil {
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 {
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 {
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 {
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
d, _ := blockDb.Get([]byte("ProtocolVersion"))
protov := int(common.NewValue(d).Uint())
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)
}
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.blockProcessor = core.NewBlockProcessor(stateDb, extraDb, eth.pow, eth.txPool, eth.chainManager, eth.EventMux())
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.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)
}
func (s *Ethereum) StartMining() error {
func (s *Ethereum) StartMining(threads int) error {
eb, err := s.Etherbase()
if err != nil {
err = fmt.Errorf("Cannot start mining without etherbase address: %v", err)
@ -376,21 +379,24 @@ func (s *Ethereum) StartMining() error {
return err
}
go s.miner.Start(eb)
go s.miner.Start(eb, threads)
return nil
}
func (s *Ethereum) Etherbase() (eb common.Address, err error) {
eb = s.etherbase
if (eb == common.Address{}) {
var ebbytes []byte
ebbytes, err = s.accountManager.Primary()
eb = common.BytesToAddress(ebbytes)
if (eb == common.Address{}) {
primary, err := s.accountManager.Primary()
if err != nil {
return eb, err
}
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() }
@ -451,6 +457,8 @@ func (s *Ethereum) Start() error {
return nil
}
// sync databases every minute. If flushing fails we exit immediatly. The system
// may not continue under any circumstances.
func (s *Ethereum) syncDatabases() {
ticker := time.NewTicker(1 * time.Minute)
done:
@ -459,13 +467,13 @@ done:
case <-ticker.C:
// don't change the order of database flushes
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 {
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 {
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:
break done
@ -537,7 +545,7 @@ func (self *Ethereum) syncAccounts(tx *types.Transaction) {
return
}
if self.accountManager.HasAccount(from.Bytes()) {
if self.accountManager.HasAccount(from) {
if self.chainManager.TxState().GetNonce(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")
errBadPeer = errors.New("action from bad peer ignored")
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")
errEmptyHashSet = errors.New("empty hash set by peer")
errPeersUnavailable = errors.New("no peers available or all peers tried for block download process")
@ -49,12 +49,6 @@ type blockPack struct {
blocks []*types.Block
}
type syncPack struct {
peer *peer
hash common.Hash
ignoreInitial bool
}
type hashPack struct {
peerId string
hashes []common.Hash
@ -63,7 +57,7 @@ type hashPack struct {
type Downloader struct {
mu sync.RWMutex
queue *queue
peers peers
peers *peerSet
activePeer string
// Callbacks
@ -83,7 +77,7 @@ type Downloader struct {
func New(hasBlock hashCheckFn, getBlock getBlockFn) *Downloader {
downloader := &Downloader{
queue: newQueue(),
peers: make(peers),
peers: newPeerSet(),
hasBlock: hasBlock,
getBlock: getBlock,
newPeerCh: make(chan *peer, 1),
@ -98,29 +92,26 @@ func (d *Downloader) Stats() (current int, max int) {
return d.queue.Size()
}
func (d *Downloader) RegisterPeer(id string, hash common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) error {
d.mu.Lock()
defer d.mu.Unlock()
glog.V(logger.Detail).Infoln("Register peer", id)
// Create a new peer and add it to the list of known peers
peer := newPeer(id, hash, getHashes, getBlocks)
// add peer to our peer set
d.peers[id] = peer
// broadcast new peer
// RegisterPeer injects a new download peer into the set of block source to be
// used for fetching hashes and blocks from.
func (d *Downloader) RegisterPeer(id string, head common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) error {
glog.V(logger.Detail).Infoln("Registering peer", id)
if err := d.peers.Register(newPeer(id, head, getHashes, getBlocks)); err != nil {
glog.V(logger.Error).Infoln("Register failed:", err)
return err
}
return nil
}
// UnregisterPeer unregisters a peer. This will prevent any action from the specified peer.
func (d *Downloader) UnregisterPeer(id string) {
d.mu.Lock()
defer d.mu.Unlock()
glog.V(logger.Detail).Infoln("Unregister peer", id)
delete(d.peers, id)
// UnregisterPeer remove a peer from the known list, preventing any action from
// the specified peer.
func (d *Downloader) UnregisterPeer(id string) error {
glog.V(logger.Detail).Infoln("Unregistering peer", id)
if err := d.peers.Unregister(id); err != nil {
glog.V(logger.Error).Infoln("Unregister failed:", err)
return err
}
return nil
}
// 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
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.peers.Reset()
// Retrieve the origin peer and initiate the downloading process
p := d.peers[id]
p := d.peers.Peer(id)
if p == nil {
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
@ -167,7 +159,9 @@ func (d *Downloader) Has(hash common.Hash) bool {
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
defer func() {
// 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)
// Start the fetcher. This will block the update entirely
// interupts need to be send to the appropriate channels
// respectively.
if err = d.startFetchingHashes(p, hash, ignoreInitial); err != nil {
if err = d.fetchHashes(p, hash); err != nil {
return err
}
// 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 {
if err = d.fetchBlocks(); err != nil {
return err
}
glog.V(logger.Debug).Infoln("Synchronization completed")
return nil
@ -234,17 +219,14 @@ blockDone:
}
// 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)
start := time.Now()
// We ignore the initial hash in some cases (e.g. we received a block without it's parent)
// In such circumstances we don't need to download the block so don't add it to the queue.
if !ignoreInitial {
// Add the hash to the queue first
d.queue.Insert([]common.Hash{h})
}
// Get the first batch of hashes
p.getHashes(h)
@ -308,20 +290,18 @@ out:
// 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
// a fair job. This is always either correct or false incorrect.
for id, peer := range d.peers {
if d.queue.Has(peer.recentHash) && !attemptedPeers[id] {
for _, peer := range d.peers.AllPeers() {
if d.queue.Has(peer.head) && !attemptedPeers[p.id] {
p = peer
break
}
}
// if all peers have been tried, abort the process entirely or if the hash is
// the zero hash.
if p == nil || (hash == common.Hash{}) {
d.queue.Reset()
return ErrTimeout
}
// set p to the active peer. this will invalidate any hashes that may be returned
// by our previous (delayed) peer.
activePeer = p
@ -334,14 +314,11 @@ out:
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)")
// 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()
// default ticker for re-fetching blocks every now and then
@ -354,19 +331,19 @@ out:
case blockPack := <-d.blockCh:
// If the peer was previously banned and failed to deliver it's pack
// in a reasonable time frame, ignore it's message.
if d.peers[blockPack.peerId] != nil {
err := d.queue.Deliver(blockPack.peerId, blockPack.blocks)
if err != nil {
glog.V(logger.Debug).Infof("deliver failed for peer %s: %v\n", blockPack.peerId, err)
// FIXME d.UnregisterPeer(blockPack.peerId)
if peer := d.peers.Peer(blockPack.peerId); peer != nil {
// Deliver the received chunk of blocks, but drop the peer if invalid
if err := d.queue.Deliver(blockPack.peerId, blockPack.blocks); err != nil {
glog.V(logger.Debug).Infof("Failed delivery for peer %s: %v\n", blockPack.peerId, err)
peer.Demote()
break
}
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()
d.peers.setState(blockPack.peerId, idleState)
// Promote the peer and update it's idle state
peer.Promote()
peer.SetIdle()
}
case <-ticker.C:
// Check for bad peers. Bad peers may indicate a peer not responding
@ -381,13 +358,12 @@ out:
// 1) Time for them to respond;
// 2) Measure their speed;
// 3) Amount and availability.
if peer := d.peers[pid]; peer != nil {
peer.demote()
peer.reset()
if peer := d.peers.Peer(pid); peer != nil {
peer.Demote()
}
}
// 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()
return errNoPeers
}
@ -398,31 +374,33 @@ out:
if d.queue.Throttle() {
continue
}
availablePeers := d.peers.get(idleState)
for _, peer := range availablePeers {
// Send a download request to all idle peers, until throttled
idlePeers := d.peers.IdlePeers()
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
// could be returned due to no hashes available.
request := d.queue.Reserve(peer, maxBlockFetch)
if request == nil {
continue
}
// XXX make fetch blocking.
// 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
// the queue
if err := peer.fetch(request); err != nil {
// log for tracing
glog.V(logger.Debug).Infof("peer %s received double work (state = %v)\n", peer.id, peer.state)
if err := peer.Fetch(request); err != nil {
glog.V(logger.Error).Infof("Peer %s received double work\n", peer.id)
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
if d.queue.InFlight() == 0 {
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 {

View File

@ -229,7 +229,7 @@ func TestThrottling(t *testing.T) {
minDesiredPeerCount = 4
blockTtl = 1 * time.Second
targetBlocks := 4 * blockCacheLimit
targetBlocks := 16 * blockCacheLimit
hashes := createHashes(0, targetBlocks)
blocks := createBlocksFromHashes(hashes)
tester := newTester(t, hashes, blocks)
@ -256,6 +256,7 @@ func TestThrottling(t *testing.T) {
return
default:
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
import (
"errors"
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"gopkg.in/fatih/set.v0"
)
const (
workingState = 2
idleState = 4
)
type hashFetcherFn func(common.Hash) error
type blockFetcherFn func([]common.Hash) error
// XXX make threadsafe!!!!
type peers map[string]*peer
var (
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() {
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
// peer represents an active peer from which hashes and blocks are retrieved.
type peer struct {
state int // Peer state (working, idle)
rep int // TODO peer reputation
id string // Unique identifier of the peer
head common.Hash // Hash of the peers latest known block
idle int32 // Current activity state of the peer (idle = 0, active = 1)
rep int32 // Simple peer reputation (not used currently)
mu sync.RWMutex
id string
recentHash common.Hash
ignored *set.Set
@ -65,31 +37,31 @@ type peer struct {
getBlocks blockFetcherFn
}
// create a new peer
func newPeer(id string, hash common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) *peer {
// newPeer create a new downloader peer, with specific hash and block retrieval
// mechanisms.
func newPeer(id string, head common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) *peer {
return &peer{
id: id,
recentHash: hash,
head: head,
getHashes: getHashes,
getBlocks: getBlocks,
state: idleState,
ignored: set.New(),
}
}
// fetch a chunk using the peer
func (p *peer) fetch(request *fetchRequest) error {
p.mu.Lock()
defer p.mu.Unlock()
// Reset clears the internal state of a peer entity.
func (p *peer) Reset() {
atomic.StoreInt32(&p.idle, 0)
p.ignored.Clear()
}
if p.state == workingState {
return errors.New("peer already fetching chunk")
// Fetch sends a block retrieval request to the remote peer.
func (p *peer) Fetch(request *fetchRequest) error {
// Short circuit if the peer is already fetching
if !atomic.CompareAndSwapInt32(&p.idle, 0, 1) {
return errAlreadyFetching
}
// set working state
p.state = workingState
// Convert the hash set to a fetchable slice
// Convert the hash set to a retrievable slice
hashes := make([]common.Hash, 0, len(request.Hashes))
for hash, _ := range request.Hashes {
hashes = append(hashes, hash)
@ -99,27 +71,127 @@ func (p *peer) fetch(request *fetchRequest) error {
return nil
}
// promote increases the peer's reputation
func (p *peer) promote() {
p.mu.Lock()
defer p.mu.Unlock()
p.rep++
// SetIdle sets the peer to idle, allowing it to execute new retrieval requests.
func (p *peer) SetIdle() {
atomic.StoreInt32(&p.idle, 0)
}
// demote decreases the peer's reputation or leaves it at 0
func (p *peer) demote() {
p.mu.Lock()
defer p.mu.Unlock()
// Promote increases the peer's reputation.
func (p *peer) Promote() {
atomic.AddInt32(&p.rep, 1)
}
if p.rep > 1 {
p.rep -= 2
} else {
p.rep = 0
// Demote decreases the peer's reputation or leaves it at 0.
func (p *peer) Demote() {
for {
// 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() {
p.state = idleState
p.ignored.Clear()
// peerSet represents the collection of active peer participating in the block
// download procedure.
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
import (
@ -8,6 +11,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"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"
)
@ -126,6 +131,10 @@ func (q *queue) Insert(hashes []common.Hash) {
for i, hash := range hashes {
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.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
peers = peers[:int(math.Sqrt(float64(len(peers))))]
//FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
for _, peer := range peers {
peer.sendTransaction(tx)
}

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
)
@ -35,7 +35,7 @@ func main() {
m := make(map[string]setting)
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*/)
if err != nil {
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"
)
type CpuMiner struct {
type CpuAgent struct {
chMu sync.Mutex
c chan *types.Block
quit chan struct{}
@ -21,8 +21,8 @@ type CpuMiner struct {
pow pow.PoW
}
func NewCpuMiner(index int, pow pow.PoW) *CpuMiner {
miner := &CpuMiner{
func NewCpuAgent(index int, pow pow.PoW) *CpuAgent {
miner := &CpuAgent{
pow: pow,
index: index,
}
@ -30,16 +30,16 @@ func NewCpuMiner(index int, pow pow.PoW) *CpuMiner {
return miner
}
func (self *CpuMiner) Work() chan<- *types.Block { return self.c }
func (self *CpuMiner) Pow() pow.PoW { return self.pow }
func (self *CpuMiner) SetReturnCh(ch chan<- *types.Block) { self.returnCh = ch }
func (self *CpuAgent) Work() chan<- *types.Block { return self.c }
func (self *CpuAgent) Pow() pow.PoW { return self.pow }
func (self *CpuAgent) SetReturnCh(ch chan<- *types.Block) { self.returnCh = ch }
func (self *CpuMiner) Stop() {
func (self *CpuAgent) Stop() {
close(self.quit)
close(self.quitCurrentOp)
}
func (self *CpuMiner) Start() {
func (self *CpuAgent) Start() {
self.quit = make(chan struct{})
self.quitCurrentOp = make(chan struct{}, 1)
self.c = make(chan *types.Block, 1)
@ -47,7 +47,7 @@ func (self *CpuMiner) Start() {
go self.update()
}
func (self *CpuMiner) update() {
func (self *CpuAgent) update() {
out:
for {
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)
// 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()
}

View File

@ -7,6 +7,8 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"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"
)
@ -21,16 +23,8 @@ type Miner struct {
pow pow.PoW
}
func New(eth core.Backend, pow pow.PoW, minerThreads int) *Miner {
// note: minerThreads is currently ignored because
// 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 New(eth core.Backend, pow pow.PoW) *Miner {
return &Miner{eth: eth, pow: pow, worker: newWorker(common.Address{}, eth)}
}
func (self *Miner) Mining() bool {
@ -46,13 +40,27 @@ func (m *Miner) SetGasPrice(price *big.Int) {
m.worker.gasPrice = price
}
func (self *Miner) Start(coinbase common.Address) {
func (self *Miner) Start(coinbase common.Address, threads int) {
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.start()
self.worker.commitNewWork()
}
func (self *Miner) Stop() {
self.worker.stop()
self.mining = false
}
func (self *Miner) Register(agent Agent) {
if self.mining {
agent.Start()
@ -61,11 +69,6 @@ func (self *Miner) Register(agent Agent) {
self.worker.register(agent)
}
func (self *Miner) Stop() {
self.mining = false
self.worker.stop()
}
func (self *Miner) HashRate() int64 {
return self.worker.HashRate()
}

View File

@ -64,13 +64,13 @@ func (a *RemoteAgent) GetWork() [3]string {
res[0] = a.work.HashNoNonce().Hex()
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
n := big.NewInt(1)
n.Lsh(n, 255)
n.Div(n, a.work.Difficulty())
n.Lsh(n, 1)
res[2] = common.Bytes2Hex(n.Bytes())
res[2] = common.BytesToHash(n.Bytes()).Hex()
}
return res

View File

@ -7,6 +7,7 @@ import (
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
@ -20,15 +21,41 @@ import (
var jsonlogger = logger.NewJsonLogger()
type environment struct {
totalUsedGas *big.Int
state *state.StateDB
coinbase *state.StateObject
block *types.Block
family *set.Set
uncles *set.Set
// Work holds the current work
type Work struct {
Number uint64
Nonce uint64
MixDigest []byte
SeedHash []byte
}
// 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 {
state := state.New(block.Root(), eth.StateDb())
env := &environment{
@ -43,21 +70,7 @@ func env(block *types.Block, eth core.Backend) *environment {
return env
}
type Work struct {
Number uint64
Nonce uint64
MixDigest []byte
SeedHash []byte
}
type Agent interface {
Work() chan<- *types.Block
SetReturnCh(chan<- *types.Block)
Stop()
Start()
GetHashRate() int64
}
// worker is the main object which takes care of applying messages to the new state
type worker struct {
mu sync.Mutex
@ -128,12 +141,12 @@ func (self *worker) start() {
self.mu.Lock()
defer self.mu.Unlock()
atomic.StoreInt32(&self.mining, 1)
// spin up agents
for _, agent := range self.agents {
agent.Start()
}
atomic.StoreInt32(&self.mining, 1)
}
func (self *worker) stop() {
@ -141,11 +154,17 @@ func (self *worker) stop() {
defer self.mu.Unlock()
if atomic.LoadInt32(&self.mining) == 1 {
var keep []Agent
// stop all agents
for _, agent := range self.agents {
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.atWork, 0)
@ -174,8 +193,11 @@ out:
self.possibleUncles[ev.Block.Hash()] = ev.Block
self.uncleMu.Unlock()
case core.TxPreEvent:
// Apply transaction to the pending state if we're not mining
if atomic.LoadInt32(&self.mining) == 0 {
self.commitNewWork()
self.mu.Lock()
self.commitTransactions(types.Transactions{ev.Tx})
self.mu.Unlock()
}
}
case <-self.quit:
@ -241,19 +263,33 @@ func (self *worker) makeCurrent() {
}
block.Header().Extra = self.extra
self.current = env(block, self.eth)
current := env(block, self.eth)
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())
self.current.coinbase.SetGasPool(core.CalcGasLimit(parent))
parent := self.chain.GetBlock(current.block.ParentHash())
current.coinbase.SetGasPool(core.CalcGasLimit(parent))
self.current = current
}
func (w *worker) setGasPrice(p *big.Int) {
w.mu.Lock()
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() {
@ -265,68 +301,14 @@ func (self *worker) commitNewWork() {
defer self.currentMu.Unlock()
self.makeCurrent()
current := self.current
transactions := self.eth.TxPool().GetTransactions()
sort.Sort(types.TxByNonce{transactions})
// Keep track of transactions which return errors so they can be removed
var (
remove = set.New()
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++
}
}
// commit transactions for this run
self.commitTransactions(transactions)
self.eth.TxPool().RemoveTransactions(current.lowGasTxs)
var (
uncles []*types.Header
@ -352,7 +334,7 @@ func (self *worker) commitNewWork() {
// We only care about logging if we're actually mining
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 {
@ -392,6 +374,71 @@ func (self *worker) commitUncle(uncle *types.Header) error {
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 {
snap := self.current.state.Copy()
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))
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"
"net"
"os"
"path"
"path/filepath"
"reflect"
"runtime"
"sync"
@ -88,7 +88,7 @@ func (test *udpTest) waitPacketOut(validate interface{}) error {
func (test *udpTest) errorf(format string, args ...interface{}) error {
_, file, line, ok := runtime.Caller(2) // errorf + waitPacketOut
if ok {
file = path.Base(file)
file = filepath.Base(file)
} else {
file = "???"
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)
*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":
args := new(NewTxArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
@ -175,16 +186,24 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
return err
}
*reply = v
case "eth_call":
args := new(CallArgs)
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)
case "eth_estimateGas":
_, gas, err := api.doCall(req.Params)
if err != nil {
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
if v == "0x0" {
*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))
case "eth_getWork":
api.xeth().SetMining(true)
api.xeth().SetMining(true, 0)
*reply = api.xeth().RemoteMining().GetWork()
case "eth_submitWork":
args := new(SubmitWorkArgs)
@ -439,10 +458,18 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
*reply = newHexData(res)
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
*reply = api.xeth().WhisperVersion()
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
args := new(WhisperMessageArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
@ -455,10 +482,18 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
*reply = true
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
*reply = api.xeth().Whisper().NewIdentity()
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
args := new(WhisperIdentityArgs)
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)
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
args := new(WhisperFilterArgs)
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())
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
args := new(FilterIdArgs)
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)
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
args := new(FilterIdArgs)
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)
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
args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
*reply = api.xeth().WhisperMessages(args.Id)
case "eth_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)
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) {
t.Skip()
solc, err := compiler.New("")
if solc == nil {
@ -45,7 +46,7 @@ func TestCompileSolidity(t *testing.T) {
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"}]`
expUserDoc := `{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}}`
expDeveloperDoc := `{"methods":{}}`
@ -75,9 +76,11 @@ func TestCompileSolidity(t *testing.T) {
t.Errorf("expected no error, got %v", err)
}
/*
if contract.Code != expCode {
t.Errorf("Expected %s got %s", expCode, contract.Code)
}
*/
if strconv.Quote(contract.Info.Source) != `"`+expSource+`"` {
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
}
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) {
var obj []json.RawMessage
var ext struct {

View File

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

View File

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

View File

@ -18,6 +18,7 @@ package rpc
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
@ -117,7 +118,13 @@ func newHexData(input interface{}) *hexdata {
binary.BigEndian.PutUint32(buff, input)
d.data = buff
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:
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 {
err string
}

View File

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

View File

@ -113,7 +113,7 @@ func (t *BlockTest) InsertPreState(ethereum *eth.Ethereum) (*state.StateDB, erro
if acct.PrivateKey != "" {
privkey, err := hex.DecodeString(strings.TrimPrefix(acct.PrivateKey, "0x"))
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 {
return nil, err
}

View File

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

View File

@ -79,7 +79,6 @@ func New(eth *eth.Ethereum, frontend Frontend) *XEth {
xeth := &XEth{
backend: eth,
frontend: frontend,
whisper: NewWhisper(eth.Whisper()),
quit: make(chan struct{}),
filterManager: filter.NewFilterManager(eth.EventMux()),
logQueue: make(map[int]*logQueue),
@ -88,6 +87,9 @@ func New(eth *eth.Ethereum, frontend Frontend) *XEth {
messages: make(map[int]*whisperFilter),
agent: miner.NewRemoteAgent(),
}
if eth.Whisper() != nil {
xeth.whisper = NewWhisper(eth.Whisper())
}
eth.Miner().Register(xeth.agent)
if frontend == nil {
xeth.frontend = dummyFrontend{}
@ -363,7 +365,7 @@ func (self *XEth) Accounts() []string {
accounts, _ := self.backend.AccountManager().Accounts()
accountAddresses := make([]string, len(accounts))
for i, ac := range accounts {
accountAddresses[i] = common.ToHex(ac.Address)
accountAddresses[i] = ac.Address.Hex()
}
return accountAddresses
}
@ -423,10 +425,10 @@ func (self *XEth) ClientVersion() string {
return self.backend.ClientVersion()
}
func (self *XEth) SetMining(shouldmine bool) bool {
func (self *XEth) SetMining(shouldmine bool, threads int) bool {
ismining := self.backend.IsMining()
if shouldmine && !ismining {
err := self.backend.StartMining()
err := self.backend.StartMining(threads)
return err == nil
}
if ismining && !shouldmine {
@ -771,7 +773,7 @@ func (self *XEth) PushTx(encodedTx string) (string, error) {
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()
var from *state.StateObject
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 {
from = statedb.GetOrNewStateObject(common.Address{})
} else {
from = statedb.GetOrNewStateObject(common.BytesToAddress(accounts[0].Address))
from = statedb.GetOrNewStateObject(accounts[0].Address)
}
} else {
from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr))
}
from.SetGasPool(self.backend.ChainManager().GasLimit())
msg := callmsg{
from: from,
to: common.HexToAddress(toStr),
@ -805,14 +808,43 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st
block := self.CurrentBlock()
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)
return common.ToHex(res), err
res, gas, err := core.ApplyMessage(vmenv, msg, from)
return common.ToHex(res), gas.String(), err
}
func (self *XEth) ConfirmTransaction(tx string) bool {
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) {
// 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 {
sig, err := self.backend.AccountManager().Sign(accounts.Account{Address: from.Bytes()}, tx.Hash().Bytes())
if err == accounts.ErrLocked {
if didUnlock {
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 {
hash := tx.Hash()
sig, err := self.doSign(from, hash, didUnlock)
if err != nil {
return err
}
tx.SetSignatureValues(sig)