aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorobscuren <geffobscura@gmail.com>2015-05-27 08:07:03 +0800
committerobscuren <geffobscura@gmail.com>2015-05-27 08:07:03 +0800
commit70867904a0255bd044851585a9ad2dc34391ced2 (patch)
tree9c5dfc6a9b8944c37f874a81cc9f8fd3d697ad4e
parentceea1a7051ddc7d2660117204aeb3392b7cf8314 (diff)
parent2c532a7255919bc09e01cca6866bfc15682509a3 (diff)
downloadgo-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.tar.gz
go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.tar.zst
go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.zip
Merge branch 'release/0.9.25'v0.9.25
-rw-r--r--cmd/geth/admin.go70
-rw-r--r--cmd/geth/js.go2
-rw-r--r--cmd/geth/js_test.go4
-rw-r--r--cmd/geth/main.go15
-rw-r--r--cmd/utils/flags.go6
-rw-r--r--common/compiler/solidity.go2
-rw-r--r--core/block_processor.go7
-rw-r--r--core/chain_manager.go2
-rw-r--r--core/transaction_pool.go5
-rw-r--r--core/transaction_pool_test.go14
-rw-r--r--eth/backend.go12
-rw-r--r--eth/downloader/downloader.go2
-rw-r--r--eth/sync.go1
-rw-r--r--jsre/jsre.go235
-rw-r--r--jsre/jsre_test.go12
-rw-r--r--p2p/discover/database.go17
-rw-r--r--p2p/discover/database_test.go11
-rw-r--r--p2p/discover/table.go131
-rw-r--r--p2p/server.go49
-rw-r--r--rpc/jeth.go6
20 files changed, 361 insertions, 242 deletions
diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go
index 4c8f110e4..8f9a009d7 100644
--- a/cmd/geth/admin.go
+++ b/cmd/geth/admin.go
@@ -88,6 +88,7 @@ func (js *jsre) adminBindings() {
debug.Set("getBlockRlp", js.getBlockRlp)
debug.Set("setHead", js.setHead)
debug.Set("processBlock", js.debugBlock)
+ debug.Set("seedhash", js.seedHash)
// undocumented temporary
debug.Set("waitForBlocks", js.waitForBlocks)
}
@@ -118,6 +119,27 @@ func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) {
return block, nil
}
+func (js *jsre) seedHash(call otto.FunctionCall) otto.Value {
+ if len(call.ArgumentList) > 0 {
+ if call.Argument(0).IsNumber() {
+ num, _ := call.Argument(0).ToInteger()
+ hash, err := ethash.GetSeedHash(uint64(num))
+ if err != nil {
+ fmt.Println(err)
+ return otto.UndefinedValue()
+ }
+ v, _ := call.Otto.ToValue(fmt.Sprintf("0x%x", hash))
+ return v
+ } else {
+ fmt.Println("arg not a number")
+ }
+ } else {
+ fmt.Println("requires number argument")
+ }
+
+ return otto.UndefinedValue()
+}
+
func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
txs := js.ethereum.TxPool().GetTransactions()
@@ -144,7 +166,8 @@ func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
}
}
- return js.re.ToVal(ltxs)
+ v, _ := call.Otto.ToValue(ltxs)
+ return v
}
func (js *jsre) resend(call otto.FunctionCall) otto.Value {
@@ -175,7 +198,8 @@ func (js *jsre) resend(call otto.FunctionCall) otto.Value {
}
js.ethereum.TxPool().RemoveTransactions(types.Transactions{tx.tx})
- return js.re.ToVal(ret)
+ v, _ := call.Otto.ToValue(ret)
+ return v
}
fmt.Println("first argument must be a transaction")
@@ -198,12 +222,13 @@ func (js *jsre) sign(call otto.FunctionCall) otto.Value {
fmt.Println(err)
return otto.UndefinedValue()
}
- v, err := js.xeth.Sign(signer, data, false)
+ signed, err := js.xeth.Sign(signer, data, false)
if err != nil {
fmt.Println(err)
return otto.UndefinedValue()
}
- return js.re.ToVal(v)
+ v, _ := call.Otto.ToValue(signed)
+ return v
}
func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value {
@@ -217,10 +242,11 @@ func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value {
vm.Debug = true
_, err = js.ethereum.BlockProcessor().RetryProcess(block)
if err != nil {
- glog.Infoln(err)
+ fmt.Println(err)
}
vm.Debug = old
+ fmt.Println("ok")
return otto.UndefinedValue()
}
@@ -237,8 +263,8 @@ func (js *jsre) setHead(call otto.FunctionCall) otto.Value {
func (js *jsre) downloadProgress(call otto.FunctionCall) otto.Value {
current, max := js.ethereum.Downloader().Stats()
-
- return js.re.ToVal(fmt.Sprintf("%d/%d", current, max))
+ v, _ := call.Otto.ToValue(fmt.Sprintf("%d/%d", current, max))
+ return v
}
func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value {
@@ -248,7 +274,8 @@ func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue()
}
encoded, _ := rlp.EncodeToBytes(block)
- return js.re.ToVal(fmt.Sprintf("%x", encoded))
+ v, _ := call.Otto.ToValue(fmt.Sprintf("%x", encoded))
+ return v
}
func (js *jsre) setExtra(call otto.FunctionCall) otto.Value {
@@ -278,8 +305,9 @@ func (js *jsre) setGasPrice(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue()
}
-func (js *jsre) hashrate(otto.FunctionCall) otto.Value {
- return js.re.ToVal(js.ethereum.Miner().HashRate())
+func (js *jsre) hashrate(call otto.FunctionCall) otto.Value {
+ v, _ := call.Otto.ToValue(js.ethereum.Miner().HashRate())
+ return v
}
func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value {
@@ -495,15 +523,18 @@ 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(acct.Address.Hex())
+ v, _ := call.Otto.ToValue(acct.Address.Hex())
+ return v
}
func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value {
- return js.re.ToVal(js.ethereum.NodeInfo())
+ v, _ := call.Otto.ToValue(js.ethereum.NodeInfo())
+ return v
}
func (js *jsre) peers(call otto.FunctionCall) otto.Value {
- return js.re.ToVal(js.ethereum.PeersInfo())
+ v, _ := call.Otto.ToValue(js.ethereum.PeersInfo())
+ return v
}
func (js *jsre) importChain(call otto.FunctionCall) otto.Value {
@@ -562,7 +593,8 @@ func (js *jsre) dumpBlock(call otto.FunctionCall) otto.Value {
statedb := state.New(block.Root(), js.ethereum.StateDb())
dump := statedb.RawDump()
- return js.re.ToVal(dump)
+ v, _ := call.Otto.ToValue(dump)
+ return v
}
func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value {
@@ -611,7 +643,8 @@ func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue()
case height = <-wait:
}
- return js.re.ToVal(height.Uint64())
+ v, _ := call.Otto.ToValue(height.Uint64())
+ return v
}
func (js *jsre) sleep(call otto.FunctionCall) otto.Value {
@@ -704,8 +737,8 @@ func (js *jsre) register(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue()
}
- return js.re.ToVal(contenthash.Hex())
-
+ v, _ := call.Otto.ToValue(contenthash.Hex())
+ return v
}
func (js *jsre) registerUrl(call otto.FunctionCall) otto.Value {
@@ -764,7 +797,8 @@ func (js *jsre) getContractInfo(call otto.FunctionCall) otto.Value {
fmt.Println(err)
return otto.UndefinedValue()
}
- return js.re.ToVal(info)
+ v, _ := call.Otto.ToValue(info)
+ return v
}
func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value {
diff --git a/cmd/geth/js.go b/cmd/geth/js.go
index 342a80bd2..0fb234d45 100644
--- a/cmd/geth/js.go
+++ b/cmd/geth/js.go
@@ -104,7 +104,7 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, interactive boo
func (js *jsre) apiBindings(f xeth.Frontend) {
xe := xeth.New(js.ethereum, f)
ethApi := rpc.NewEthereumApi(xe)
- jeth := rpc.NewJeth(ethApi, js.re.ToVal, js.re)
+ jeth := rpc.NewJeth(ethApi, js.re)
js.re.Set("jeth", struct{}{})
t, _ := js.re.Get("jeth")
diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go
index 41e1034e9..dee25e44e 100644
--- a/cmd/geth/js_test.go
+++ b/cmd/geth/js_test.go
@@ -35,6 +35,7 @@ const (
var (
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
+ testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f"))
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}`
)
@@ -72,6 +73,7 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
ks := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore"))
am := accounts.NewManager(ks)
ethereum, err := eth.New(&eth.Config{
+ NodeKey: testNodeKey,
DataDir: tmp,
AccountManager: am,
MaxPeers: 0,
@@ -122,7 +124,7 @@ func TestNodeInfo(t *testing.T) {
}
defer ethereum.Stop()
defer os.RemoveAll(tmp)
- want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","NodeUrl":"enode://00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000@0.0.0.0:0","TCPPort":0,"Td":"0"}`
+ want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5","NodeUrl":"enode://4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5@0.0.0.0:0","TCPPort":0,"Td":"131072"}`
checkEvalJSON(t, repl, `admin.nodeInfo()`, want)
}
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 56f383b77..0cbf8e41a 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -48,7 +48,7 @@ import _ "net/http/pprof"
const (
ClientIdentifier = "Geth"
- Version = "0.9.24"
+ Version = "0.9.25"
)
var (
@@ -260,6 +260,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.AutoDAGFlag,
utils.NATFlag,
utils.NatspecEnabledFlag,
+ utils.NoDiscoverFlag,
utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag,
utils.RPCEnabledFlag,
@@ -532,9 +533,9 @@ func importchain(ctx *cli.Context) {
}
// force database flush
- ethereum.BlockDb().Close()
- ethereum.StateDb().Close()
- ethereum.ExtraDb().Close()
+ ethereum.BlockDb().Flush()
+ ethereum.StateDb().Flush()
+ ethereum.ExtraDb().Flush()
fmt.Printf("Import done in %v", time.Since(start))
@@ -629,9 +630,9 @@ func upgradeDb(ctx *cli.Context) {
}
// force database flush
- ethereum.BlockDb().Close()
- ethereum.StateDb().Close()
- ethereum.ExtraDb().Close()
+ ethereum.BlockDb().Flush()
+ ethereum.StateDb().Flush()
+ ethereum.ExtraDb().Flush()
os.Remove(exportFile)
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index cb774aa5b..155110ddc 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -235,6 +235,10 @@ var (
Usage: "NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>)",
Value: "any",
}
+ NoDiscoverFlag = cli.BoolFlag{
+ Name: "nodiscover",
+ Usage: "Disables the peer discovery mechanism (manual peer addition)",
+ }
WhisperEnabledFlag = cli.BoolFlag{
Name: "shh",
Usage: "Enable whisper",
@@ -312,6 +316,7 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
Port: ctx.GlobalString(ListenPortFlag.Name),
NAT: GetNAT(ctx),
NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name),
+ Discovery: !ctx.GlobalBool(NoDiscoverFlag.Name),
NodeKey: GetNodeKey(ctx),
Shh: ctx.GlobalBool(WhisperEnabledFlag.Name),
Dial: true,
@@ -320,7 +325,6 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
SolcPath: ctx.GlobalString(SolcPathFlag.Name),
AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name),
}
-
}
func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Database) {
diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go
index 0cfd41c95..caf86974e 100644
--- a/common/compiler/solidity.go
+++ b/common/compiler/solidity.go
@@ -34,6 +34,8 @@ var (
"file", //
"--natspec-dev", // Request to output the contract's Natspec developer documentation.
"file",
+ "--add-std",
+ "1",
}
)
diff --git a/core/block_processor.go b/core/block_processor.go
index 3f10e5efd..e808c03da 100644
--- a/core/block_processor.go
+++ b/core/block_processor.go
@@ -21,7 +21,7 @@ import (
const (
// must be bumped when consensus algorithm is changed, this forces the upgradedb
// command to be run (forces the blocks to be imported again using the new algorithm)
- BlockChainVersion = 2
+ BlockChainVersion = 3
)
var receiptsPre = []byte("receipts-")
@@ -159,6 +159,9 @@ func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs state.Logs, err
return nil, ParentError(header.ParentHash)
}
parent := sm.bc.GetBlock(header.ParentHash)
+ if !sm.Pow.Verify(block) {
+ return nil, ValidationError("Block's nonce is invalid (= %x)", block.Nonce)
+ }
return sm.processWithParent(block, parent)
}
@@ -299,7 +302,7 @@ func (sm *BlockProcessor) ValidateHeader(block, parent *types.Header, checkPow b
a := new(big.Int).Sub(block.GasLimit, parent.GasLimit)
a.Abs(a)
b := new(big.Int).Div(parent.GasLimit, params.GasLimitBoundDivisor)
- if !(a.Cmp(b) < 0) || (block.GasLimit.Cmp(params.MinGasLimit) == -1) {
+ if !(a.Cmp(b) <= 0) || (block.GasLimit.Cmp(params.MinGasLimit) == -1) {
return fmt.Errorf("GasLimit check failed for block %v (%v > %v)", block.GasLimit, a, b)
}
diff --git a/core/chain_manager.go b/core/chain_manager.go
index 2b86bb794..ec479db25 100644
--- a/core/chain_manager.go
+++ b/core/chain_manager.go
@@ -750,7 +750,7 @@ out:
func blockErr(block *types.Block, err error) {
h := block.Header()
- glog.V(logger.Error).Infof("INVALID block #%v (%x)\n", h.Number, h.Hash().Bytes())
+ glog.V(logger.Error).Infof("Bad block #%v (%x)\n", h.Number, h.Hash().Bytes())
glog.V(logger.Error).Infoln(err)
glog.V(logger.Debug).Infoln(block)
}
diff --git a/core/transaction_pool.go b/core/transaction_pool.go
index e68f7406a..c896488d1 100644
--- a/core/transaction_pool.go
+++ b/core/transaction_pool.go
@@ -25,6 +25,7 @@ var (
ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value")
ErrIntrinsicGas = errors.New("Intrinsic gas too low")
ErrGasLimit = errors.New("Exceeds block gas limit")
+ ErrNegativeValue = errors.New("Negative value")
)
const txPoolQueueSize = 50
@@ -125,6 +126,10 @@ func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error {
return ErrGasLimit
}
+ if tx.Amount.Cmp(common.Big0) < 0 {
+ return ErrNegativeValue
+ }
+
total := new(big.Int).Mul(tx.Price, tx.GasLimit)
total.Add(total, tx.Value())
if pool.currentState().GetBalance(from).Cmp(total) < 0 {
diff --git a/core/transaction_pool_test.go b/core/transaction_pool_test.go
index 49224be5b..d6ea4a2a9 100644
--- a/core/transaction_pool_test.go
+++ b/core/transaction_pool_test.go
@@ -138,3 +138,17 @@ func TestRemoveTx(t *testing.T) {
t.Error("expected txs to be 0, got", len(pool.txs))
}
}
+
+func TestNegativeValue(t *testing.T) {
+ pool, key := setupTxPool()
+
+ tx := transaction()
+ tx.Value().Set(big.NewInt(-1))
+ tx.SignECDSA(key)
+ from, _ := tx.From()
+ pool.currentState().AddBalance(from, big.NewInt(1))
+ err := pool.Add(tx)
+ if err != ErrNegativeValue {
+ t.Error("expected", ErrNegativeValue, "got", err)
+ }
+}
diff --git a/eth/backend.go b/eth/backend.go
index aeaac788a..18e214d44 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -72,6 +72,7 @@ type Config struct {
MaxPeers int
MaxPendingPeers int
+ Discovery bool
Port string
// Space-separated list of discovery node URLs
@@ -311,6 +312,7 @@ func New(config *Config) (*Ethereum, error) {
Name: config.Name,
MaxPeers: config.MaxPeers,
MaxPendingPeers: config.MaxPendingPeers,
+ Discovery: config.Discovery,
Protocols: protocols,
NAT: config.NAT,
NoDial: !config.Dial,
@@ -449,14 +451,10 @@ func (s *Ethereum) Start() error {
ClientString: s.net.Name,
ProtocolVersion: ProtocolVersion,
})
-
- if s.net.MaxPeers > 0 {
- err := s.net.Start()
- if err != nil {
- return err
- }
+ err := s.net.Start()
+ if err != nil {
+ return err
}
-
// periodically flush databases
go s.syncDatabases()
diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go
index 0634baaed..421c336f2 100644
--- a/eth/downloader/downloader.go
+++ b/eth/downloader/downloader.go
@@ -415,7 +415,7 @@ out:
peer.Demote()
break
}
- if glog.V(logger.Debug) {
+ if glog.V(logger.Debug) && len(blockPack.blocks) > 0 {
glog.Infof("Added %d blocks from: %s\n", len(blockPack.blocks), blockPack.peerId)
}
// Promote the peer and update it's idle state
diff --git a/eth/sync.go b/eth/sync.go
index d93f83a78..cf549f852 100644
--- a/eth/sync.go
+++ b/eth/sync.go
@@ -70,7 +70,6 @@ func (pm *ProtocolManager) processBlocks() error {
// Try to inset the blocks, drop the originating peer if there's an error
index, err := pm.chainman.InsertChain(raw)
if err != nil {
- glog.V(logger.Warn).Infof("Block insertion failed: %v", err)
pm.removePeer(blocks[index].OriginPeer)
pm.downloader.Cancel()
return err
diff --git a/jsre/jsre.go b/jsre/jsre.go
index a6dd117a3..3d648f02c 100644
--- a/jsre/jsre.go
+++ b/jsre/jsre.go
@@ -19,9 +19,7 @@ It provides some helper functions to
- bind native go objects
*/
type JSRE struct {
- assetPath string
- vm *otto.Otto
-
+ assetPath string
evalQueue chan *evalReq
stopEventLoop chan bool
loopWg sync.WaitGroup
@@ -35,68 +33,37 @@ type jsTimer struct {
call otto.FunctionCall
}
-// evalResult is a structure to store the result of any serialized vm execution
-type evalResult struct {
- result otto.Value
- err error
-}
-
-// evalReq is a serialized vm execution request put in evalQueue and processed by runEventLoop
+// evalReq is a serialized vm execution request processed by runEventLoop.
type evalReq struct {
- fn func(res *evalResult)
+ fn func(vm *otto.Otto)
done chan bool
- res evalResult
}
// runtime must be stopped with Stop() after use and cannot be used after stopping
func New(assetPath string) *JSRE {
re := &JSRE{
- assetPath: assetPath,
- vm: otto.New(),
+ assetPath: assetPath,
+ evalQueue: make(chan *evalReq),
+ stopEventLoop: make(chan bool),
}
-
- // load prettyprint func definition
- re.vm.Run(pp_js)
- re.vm.Set("loadScript", re.loadScript)
-
- re.evalQueue = make(chan *evalReq)
- re.stopEventLoop = make(chan bool)
re.loopWg.Add(1)
go re.runEventLoop()
-
+ re.Compile("pp.js", pp_js) // load prettyprint func definition
+ re.Set("loadScript", re.loadScript)
return re
}
-// this function runs a piece of JS code either in a serialized way (when useEQ is true) or instantly, circumventing the evalQueue
-func (self *JSRE) run(src interface{}, useEQ bool) (value otto.Value, err error) {
- if useEQ {
- done := make(chan bool)
- req := &evalReq{
- fn: func(res *evalResult) {
- res.result, res.err = self.vm.Run(src)
- },
- done: done,
- }
- self.evalQueue <- req
- <-done
- return req.res.result, req.res.err
- } else {
- return self.vm.Run(src)
- }
-}
+// This function runs the main event loop from a goroutine that is started
+// when JSRE is created. Use Stop() before exiting to properly stop it.
+// The event loop processes vm access requests from the evalQueue in a
+// serialized way and calls timer callback functions at the appropriate time.
-/*
-This function runs the main event loop from a goroutine that is started
- when JSRE is created. Use Stop() before exiting to properly stop it.
-The event loop processes vm access requests from the evalQueue in a
- serialized way and calls timer callback functions at the appropriate time.
-
-Exported functions always access the vm through the event queue. You can
- call the functions of the otto vm directly to circumvent the queue. These
- functions should be used if and only if running a routine that was already
- called from JS through an RPC call.
-*/
+// Exported functions always access the vm through the event queue. You can
+// call the functions of the otto vm directly to circumvent the queue. These
+// functions should be used if and only if running a routine that was already
+// called from JS through an RPC call.
func (self *JSRE) runEventLoop() {
+ vm := otto.New()
registry := map[*jsTimer]*jsTimer{}
ready := make(chan *jsTimer)
@@ -143,10 +110,10 @@ func (self *JSRE) runEventLoop() {
}
return otto.UndefinedValue()
}
- self.vm.Set("setTimeout", setTimeout)
- self.vm.Set("setInterval", setInterval)
- self.vm.Set("clearTimeout", clearTimeout)
- self.vm.Set("clearInterval", clearTimeout)
+ vm.Set("setTimeout", setTimeout)
+ vm.Set("setInterval", setInterval)
+ vm.Set("clearTimeout", clearTimeout)
+ vm.Set("clearInterval", clearTimeout)
var waitForCallbacks bool
@@ -166,8 +133,7 @@ loop:
arguments = make([]interface{}, 1)
}
arguments[0] = timer.call.ArgumentList[0]
- _, err := self.vm.Call(`Function.call.call`, nil, arguments...)
-
+ _, err := vm.Call(`Function.call.call`, nil, arguments...)
if err != nil {
fmt.Println("js error:", err, arguments)
}
@@ -179,10 +145,10 @@ loop:
break loop
}
}
- case evalReq := <-self.evalQueue:
+ case req := <-self.evalQueue:
// run the code, send the result back
- evalReq.fn(&evalReq.res)
- close(evalReq.done)
+ req.fn(vm)
+ close(req.done)
if waitForCallbacks && (len(registry) == 0) {
break loop
}
@@ -201,6 +167,14 @@ loop:
self.loopWg.Done()
}
+// do schedules the given function on the event loop.
+func (self *JSRE) do(fn func(*otto.Otto)) {
+ done := make(chan bool)
+ req := &evalReq{fn, done}
+ self.evalQueue <- req
+ <-done
+}
+
// stops the event loop before exit, optionally waits for all timers to expire
func (self *JSRE) Stop(waitForCallbacks bool) {
self.stopEventLoop <- waitForCallbacks
@@ -210,119 +184,78 @@ func (self *JSRE) Stop(waitForCallbacks bool) {
// Exec(file) loads and runs the contents of a file
// if a relative path is given, the jsre's assetPath is used
func (self *JSRE) Exec(file string) error {
- return self.exec(common.AbsolutePath(self.assetPath, file), true)
-}
-
-// circumvents the eval queue, see runEventLoop
-func (self *JSRE) execWithoutEQ(file string) error {
- return self.exec(common.AbsolutePath(self.assetPath, file), false)
-}
-
-func (self *JSRE) exec(path string, useEQ bool) error {
- code, err := ioutil.ReadFile(path)
+ code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file))
if err != nil {
return err
}
- _, err = self.run(code, useEQ)
+ self.do(func(vm *otto.Otto) { _, err = vm.Run(code) })
return err
}
-// assigns value v to a variable in the JS environment
-func (self *JSRE) Bind(name string, v interface{}) (err error) {
- self.Set(name, v)
- return
+// Bind assigns value v to a variable in the JS environment
+// This method is deprecated, use Set.
+func (self *JSRE) Bind(name string, v interface{}) error {
+ return self.Set(name, v)
}
-// runs a piece of JS code
-func (self *JSRE) Run(code string) (otto.Value, error) {
- return self.run(code, true)
+// Run runs a piece of JS code.
+func (self *JSRE) Run(code string) (v otto.Value, err error) {
+ self.do(func(vm *otto.Otto) { v, err = vm.Run(code) })
+ return v, err
}
-// returns the value of a variable in the JS environment
-func (self *JSRE) Get(ns string) (otto.Value, error) {
- done := make(chan bool)
- req := &evalReq{
- fn: func(res *evalResult) {
- res.result, res.err = self.vm.Get(ns)
- },
- done: done,
- }
- self.evalQueue <- req
- <-done
- return req.res.result, req.res.err
+// Get returns the value of a variable in the JS environment.
+func (self *JSRE) Get(ns string) (v otto.Value, err error) {
+ self.do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
+ return v, err
}
-// assigns value v to a variable in the JS environment
-func (self *JSRE) Set(ns string, v interface{}) error {
- done := make(chan bool)
- req := &evalReq{
- fn: func(res *evalResult) {
- res.err = self.vm.Set(ns, v)
- },
- done: done,
- }
- self.evalQueue <- req
- <-done
- return req.res.err
+// Set assigns value v to a variable in the JS environment.
+func (self *JSRE) Set(ns string, v interface{}) (err error) {
+ self.do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
+ return err
}
-/*
-Executes a JS script from inside the currently executing JS code.
-Should only be called from inside an RPC routine.
-*/
+// loadScript executes a JS script from inside the currently executing JS code.
func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
file, err := call.Argument(0).ToString()
if err != nil {
+ // TODO: throw exception
+ return otto.FalseValue()
+ }
+ file = common.AbsolutePath(self.assetPath, file)
+ source, err := ioutil.ReadFile(file)
+ if err != nil {
+ // TODO: throw exception
return otto.FalseValue()
}
- if err := self.execWithoutEQ(file); err != nil { // loadScript is only called from inside js
+ if _, err := compileAndRun(call.Otto, file, source); err != nil {
+ // TODO: throw exception
fmt.Println("err:", err)
return otto.FalseValue()
}
-
+ // TODO: return evaluation result
return otto.TrueValue()
}
-// uses the "prettyPrint" JS function to format a value
+// PrettyPrint writes v to standard output.
func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
var method otto.Value
- v, err = self.ToValue(v)
- if err != nil {
- return
- }
- method, err = self.vm.Get("prettyPrint")
- if err != nil {
- return
- }
- return method.Call(method, v)
-}
-
-// creates an otto value from a go type (serialized version)
-func (self *JSRE) ToValue(v interface{}) (otto.Value, error) {
- done := make(chan bool)
- req := &evalReq{
- fn: func(res *evalResult) {
- res.result, res.err = self.vm.ToValue(v)
- },
- done: done,
- }
- self.evalQueue <- req
- <-done
- return req.res.result, req.res.err
-}
-
-// creates an otto value from a go type (non-serialized version)
-func (self *JSRE) ToVal(v interface{}) otto.Value {
-
- result, err := self.vm.ToValue(v)
- if err != nil {
- fmt.Println("Value unknown:", err)
- return otto.UndefinedValue()
- }
- return result
+ self.do(func(vm *otto.Otto) {
+ val, err = vm.ToValue(v)
+ if err != nil {
+ return
+ }
+ method, err = vm.Get("prettyPrint")
+ if err != nil {
+ return
+ }
+ val, err = method.Call(method, val)
+ })
+ return val, err
}
-// evaluates JS function and returns result in a pretty printed string format
+// Eval evaluates JS function and returns result in a pretty printed string format.
func (self *JSRE) Eval(code string) (s string, err error) {
var val otto.Value
val, err = self.Run(code)
@@ -336,12 +269,16 @@ func (self *JSRE) Eval(code string) (s string, err error) {
return fmt.Sprintf("%v", val), nil
}
-// compiles and then runs a piece of JS code
-func (self *JSRE) Compile(fn string, src interface{}) error {
- script, err := self.vm.Compile(fn, src)
+// Compile compiles and then runs a piece of JS code.
+func (self *JSRE) Compile(filename string, src interface{}) (err error) {
+ self.do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
+ return err
+}
+
+func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
+ script, err := vm.Compile(filename, src)
if err != nil {
- return err
+ return otto.Value{}, err
}
- self.run(script, true)
- return nil
+ return vm.Run(script)
}
diff --git a/jsre/jsre_test.go b/jsre/jsre_test.go
index 5eaca2b91..42308de88 100644
--- a/jsre/jsre_test.go
+++ b/jsre/jsre_test.go
@@ -1,16 +1,15 @@
package jsre
import (
- "github.com/robertkrimen/otto"
"io/ioutil"
"os"
"testing"
"time"
+
+ "github.com/robertkrimen/otto"
)
-type testNativeObjectBinding struct {
- toVal func(interface{}) otto.Value
-}
+type testNativeObjectBinding struct{}
type msg struct {
Msg string
@@ -21,7 +20,8 @@ func (no *testNativeObjectBinding) TestMethod(call otto.FunctionCall) otto.Value
if err != nil {
return otto.UndefinedValue()
}
- return no.toVal(&msg{m})
+ v, _ := call.Otto.ToValue(&msg{m})
+ return v
}
func TestExec(t *testing.T) {
@@ -74,7 +74,7 @@ func TestNatto(t *testing.T) {
func TestBind(t *testing.T) {
jsre := New("/tmp")
- jsre.Bind("no", &testNativeObjectBinding{jsre.ToVal})
+ jsre.Bind("no", &testNativeObjectBinding{})
val, err := jsre.Run(`no.TestMethod("testMsg")`)
if err != nil {
diff --git a/p2p/discover/database.go b/p2p/discover/database.go
index 3a3f1254b..1b73c3dea 100644
--- a/p2p/discover/database.go
+++ b/p2p/discover/database.go
@@ -44,9 +44,10 @@ var (
nodeDBVersionKey = []byte("version") // Version of the database to flush if changes
nodeDBItemPrefix = []byte("n:") // Identifier to prefix node entries with
- nodeDBDiscoverRoot = ":discover"
- nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping"
- nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong"
+ nodeDBDiscoverRoot = ":discover"
+ nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping"
+ nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong"
+ nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail"
)
// newNodeDB creates a new node database for storing and retrieving infos about
@@ -275,6 +276,16 @@ func (db *nodeDB) updateLastPong(id NodeID, instance time.Time) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
}
+// findFails retrieves the number of findnode failures since bonding.
+func (db *nodeDB) findFails(id NodeID) int {
+ return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
+}
+
+// updateFindFails updates the number of findnode failures since bonding.
+func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
+ return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
+}
+
// querySeeds retrieves a batch of nodes to be used as potential seed servers
// during bootstrapping the node into the network.
//
diff --git a/p2p/discover/database_test.go b/p2p/discover/database_test.go
index 88f5d2155..4fce164ca 100644
--- a/p2p/discover/database_test.go
+++ b/p2p/discover/database_test.go
@@ -93,6 +93,7 @@ func TestNodeDBFetchStore(t *testing.T) {
30303,
)
inst := time.Now()
+ num := 314
db, _ := newNodeDB("", Version, NodeID{})
defer db.close()
@@ -117,6 +118,16 @@ func TestNodeDBFetchStore(t *testing.T) {
if stored := db.lastPong(node.ID); stored.Unix() != inst.Unix() {
t.Errorf("pong: value mismatch: have %v, want %v", stored, inst)
}
+ // Check fetch/store operations on a node findnode-failure object
+ if stored := db.findFails(node.ID); stored != 0 {
+ t.Errorf("find-node fails: non-existing object: %v", stored)
+ }
+ if err := db.updateFindFails(node.ID, num); err != nil {
+ t.Errorf("find-node fails: failed to update: %v", err)
+ }
+ if stored := db.findFails(node.ID); stored != num {
+ t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num)
+ }
// Check fetch/store operations on an actual node object
if stored := db.node(node.ID); stored != nil {
t.Errorf("node: non-existing object: %v", stored)
diff --git a/p2p/discover/table.go b/p2p/discover/table.go
index b523a0684..4b7ddb775 100644
--- a/p2p/discover/table.go
+++ b/p2p/discover/table.go
@@ -27,6 +27,7 @@ const (
nBuckets = hashBits + 1 // Number of buckets
maxBondingPingPongs = 16
+ maxFindnodeFailures = 5
)
type Table struct {
@@ -190,6 +191,12 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
result := tab.closest(target, bucketSize)
tab.mutex.Unlock()
+ // If the result set is empty, all nodes were dropped, refresh
+ if len(result.entries) == 0 {
+ tab.refresh()
+ return nil
+ }
+
for {
// ask the alpha closest nodes that we haven't asked yet
for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ {
@@ -198,7 +205,19 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
asked[n.ID] = true
pendingQueries++
go func() {
- r, _ := tab.net.findnode(n.ID, n.addr(), targetID)
+ // Find potential neighbors to bond with
+ r, err := tab.net.findnode(n.ID, n.addr(), targetID)
+ if err != nil {
+ // Bump the failure counter to detect and evacuate non-bonded entries
+ fails := tab.db.findFails(n.ID) + 1
+ tab.db.updateFindFails(n.ID, fails)
+ glog.V(logger.Detail).Infof("Bumping failures for %x: %d", n.ID[:8], fails)
+
+ if fails >= maxFindnodeFailures {
+ glog.V(logger.Detail).Infof("Evacuating node %x: %d findnode failures", n.ID[:8], fails)
+ tab.del(n)
+ }
+ }
reply <- tab.bondall(r)
}()
}
@@ -219,30 +238,53 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
return result.entries
}
-// refresh performs a lookup for a random target to keep buckets full.
+// refresh performs a lookup for a random target to keep buckets full, or seeds
+// the table if it is empty (initial bootstrap or discarded faulty peers).
func (tab *Table) refresh() {
- // The Kademlia paper specifies that the bucket refresh should
- // perform a refresh in the least recently used bucket. We cannot
- // adhere to this because the findnode target is a 512bit value
- // (not hash-sized) and it is not easily possible to generate a
- // sha3 preimage that falls into a chosen bucket.
- //
- // We perform a lookup with a random target instead.
- var target NodeID
- rand.Read(target[:])
- result := tab.Lookup(target)
- if len(result) == 0 {
+ seed := true
+
+ // If the discovery table is empty, seed with previously known nodes
+ tab.mutex.Lock()
+ for _, bucket := range tab.buckets {
+ if len(bucket.entries) > 0 {
+ seed = false
+ break
+ }
+ }
+ tab.mutex.Unlock()
+
+ // If the table is not empty, try to refresh using the live entries
+ if !seed {
+ // The Kademlia paper specifies that the bucket refresh should
+ // perform a refresh in the least recently used bucket. We cannot
+ // adhere to this because the findnode target is a 512bit value
+ // (not hash-sized) and it is not easily possible to generate a
+ // sha3 preimage that falls into a chosen bucket.
+ //
+ // We perform a lookup with a random target instead.
+ var target NodeID
+ rand.Read(target[:])
+
+ result := tab.Lookup(target)
+ if len(result) == 0 {
+ // Lookup failed, seed after all
+ seed = true
+ }
+ }
+
+ if seed {
// Pick a batch of previously know seeds to lookup with
seeds := tab.db.querySeeds(10)
for _, seed := range seeds {
glog.V(logger.Debug).Infoln("Seeding network with", seed)
}
- // Bootstrap the table with a self lookup
- all := tab.bondall(append(tab.nursery, seeds...))
- tab.mutex.Lock()
- tab.add(all)
- tab.mutex.Unlock()
- tab.Lookup(tab.self.ID)
+ nodes := append(tab.nursery, seeds...)
+
+ // Bond with all the seed nodes (will pingpong only if failed recently)
+ bonded := tab.bondall(nodes)
+ if len(bonded) > 0 {
+ tab.Lookup(tab.self.ID)
+ }
// TODO: the Kademlia paper says that we're supposed to perform
// random lookups in all buckets further away than our closest neighbor.
}
@@ -305,8 +347,16 @@ func (tab *Table) bondall(nodes []*Node) (result []*Node) {
// If pinged is true, the remote node has just pinged us and one half
// of the process can be skipped.
func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) (*Node, error) {
- var n *Node
- if n = tab.db.node(id); n == nil {
+ // Retrieve a previously known node and any recent findnode failures
+ node, fails := tab.db.node(id), 0
+ if node != nil {
+ fails = tab.db.findFails(id)
+ }
+ // If the node is unknown (non-bonded) or failed (remotely unknown), bond from scratch
+ var result error
+ if node == nil || fails > 0 {
+ glog.V(logger.Detail).Infof("Bonding %x: known=%v, fails=%v", id[:8], node != nil, fails)
+
tab.bondmu.Lock()
w := tab.bonding[id]
if w != nil {
@@ -325,18 +375,24 @@ func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16
delete(tab.bonding, id)
tab.bondmu.Unlock()
}
- n = w.n
- if w.err != nil {
- return nil, w.err
+ // Retrieve the bonding results
+ result = w.err
+ if result == nil {
+ node = w.n
}
}
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
- b := tab.buckets[logdist(tab.self.sha, n.sha)]
- if !b.bump(n) {
- tab.pingreplace(n, b)
+ // Even if bonding temporarily failed, give the node a chance
+ if node != nil {
+ tab.mutex.Lock()
+ defer tab.mutex.Unlock()
+
+ b := tab.buckets[logdist(tab.self.sha, node.sha)]
+ if !b.bump(node) {
+ tab.pingreplace(node, b)
+ }
+ tab.db.updateFindFails(id, 0)
}
- return n, nil
+ return node, result
}
func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) {
@@ -414,6 +470,21 @@ outer:
}
}
+// del removes an entry from the node table (used to evacuate failed/non-bonded
+// discovery peers).
+func (tab *Table) del(node *Node) {
+ tab.mutex.Lock()
+ defer tab.mutex.Unlock()
+
+ bucket := tab.buckets[logdist(tab.self.sha, node.sha)]
+ for i := range bucket.entries {
+ if bucket.entries[i].ID == node.ID {
+ bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...)
+ return
+ }
+ }
+}
+
func (b *bucket) bump(n *Node) bool {
for i := range b.entries {
if b.entries[i].ID == n.ID {
diff --git a/p2p/server.go b/p2p/server.go
index af08380e1..589041810 100644
--- a/p2p/server.go
+++ b/p2p/server.go
@@ -55,6 +55,10 @@ type Server struct {
// Zero defaults to preset values.
MaxPendingPeers int
+ // Discovery specifies whether the peer discovery mechanism should be started
+ // or not. Disabling is usually useful for protocol debugging (manual topology).
+ Discovery bool
+
// Name sets the node name of this server.
// Use common.MakeName to create a name that follows existing conventions.
Name string
@@ -237,9 +241,26 @@ func (srv *Server) AddPeer(node *discover.Node) {
func (srv *Server) Self() *discover.Node {
srv.lock.Lock()
defer srv.lock.Unlock()
+
+ // If the server's not running, return an empty node
if !srv.running {
return &discover.Node{IP: net.ParseIP("0.0.0.0")}
}
+ // If the node is running but discovery is off, manually assemble the node infos
+ if srv.ntab == nil {
+ // Inbound connections disabled, use zero address
+ if srv.listener == nil {
+ return &discover.Node{IP: net.ParseIP("0.0.0.0"), ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
+ }
+ // Otherwise inject the listener address too
+ addr := srv.listener.Addr().(*net.TCPAddr)
+ return &discover.Node{
+ ID: discover.PubkeyID(&srv.PrivateKey.PublicKey),
+ IP: addr.IP,
+ TCP: uint16(addr.Port),
+ }
+ }
+ // Otherwise return the live node infos
return srv.ntab.Self()
}
@@ -275,9 +296,6 @@ func (srv *Server) Start() (err error) {
if srv.PrivateKey == nil {
return fmt.Errorf("Server.PrivateKey must be set to a non-nil key")
}
- if srv.MaxPeers <= 0 {
- return fmt.Errorf("Server.MaxPeers must be > 0")
- }
if srv.newTransport == nil {
srv.newTransport = newRLPX
}
@@ -293,15 +311,22 @@ func (srv *Server) Start() (err error) {
srv.peerOpDone = make(chan struct{})
// node table
- ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT, srv.NodeDatabase)
- if err != nil {
- return err
+ if srv.Discovery {
+ ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT, srv.NodeDatabase)
+ if err != nil {
+ return err
+ }
+ srv.ntab = ntab
}
- srv.ntab = ntab
- dialer := newDialState(srv.StaticNodes, srv.ntab, srv.MaxPeers/2)
+
+ dynPeers := srv.MaxPeers / 2
+ if !srv.Discovery {
+ dynPeers = 0
+ }
+ dialer := newDialState(srv.StaticNodes, srv.ntab, dynPeers)
// handshake
- srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: ntab.Self().ID}
+ srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
for _, p := range srv.Protocols {
srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap())
}
@@ -457,7 +482,9 @@ running:
}
// Terminate discovery. If there is a running lookup it will terminate soon.
- srv.ntab.Close()
+ if srv.ntab != nil {
+ srv.ntab.Close()
+ }
// Disconnect all peers.
for _, p := range peers {
p.Disconnect(DiscQuitting)
@@ -489,7 +516,7 @@ func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn)
return DiscTooManyPeers
case peers[c.id] != nil:
return DiscAlreadyConnected
- case c.id == srv.ntab.Self().ID:
+ case c.id == srv.Self().ID:
return DiscSelf
default:
return nil
diff --git a/rpc/jeth.go b/rpc/jeth.go
index 2097ac30d..61be60dc7 100644
--- a/rpc/jeth.go
+++ b/rpc/jeth.go
@@ -3,18 +3,18 @@ package rpc
import (
"encoding/json"
"fmt"
+
"github.com/ethereum/go-ethereum/jsre"
"github.com/robertkrimen/otto"
)
type Jeth struct {
ethApi *EthereumApi
- toVal func(interface{}) otto.Value
re *jsre.JSRE
}
-func NewJeth(ethApi *EthereumApi, toVal func(interface{}) otto.Value, re *jsre.JSRE) *Jeth {
- return &Jeth{ethApi, toVal, re}
+func NewJeth(ethApi *EthereumApi, re *jsre.JSRE) *Jeth {
+ return &Jeth{ethApi, re}
}
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {