Merge branch 'release/0.9.25'

This commit is contained in:
obscuren 2015-05-27 02:07:03 +02:00
commit 70867904a0
20 changed files with 363 additions and 244 deletions

@ -88,6 +88,7 @@ func (js *jsre) adminBindings() {
debug.Set("getBlockRlp", js.getBlockRlp) debug.Set("getBlockRlp", js.getBlockRlp)
debug.Set("setHead", js.setHead) debug.Set("setHead", js.setHead)
debug.Set("processBlock", js.debugBlock) debug.Set("processBlock", js.debugBlock)
debug.Set("seedhash", js.seedHash)
// undocumented temporary // undocumented temporary
debug.Set("waitForBlocks", js.waitForBlocks) debug.Set("waitForBlocks", js.waitForBlocks)
} }
@ -118,6 +119,27 @@ func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) {
return block, nil 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 { func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
txs := js.ethereum.TxPool().GetTransactions() 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 { 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}) 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") fmt.Println("first argument must be a transaction")
@ -198,12 +222,13 @@ func (js *jsre) sign(call otto.FunctionCall) otto.Value {
fmt.Println(err) fmt.Println(err)
return otto.UndefinedValue() return otto.UndefinedValue()
} }
v, err := js.xeth.Sign(signer, data, false) signed, err := js.xeth.Sign(signer, data, false)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return otto.UndefinedValue() return otto.UndefinedValue()
} }
return js.re.ToVal(v) v, _ := call.Otto.ToValue(signed)
return v
} }
func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value { 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 vm.Debug = true
_, err = js.ethereum.BlockProcessor().RetryProcess(block) _, err = js.ethereum.BlockProcessor().RetryProcess(block)
if err != nil { if err != nil {
glog.Infoln(err) fmt.Println(err)
} }
vm.Debug = old vm.Debug = old
fmt.Println("ok")
return otto.UndefinedValue() 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 { func (js *jsre) downloadProgress(call otto.FunctionCall) otto.Value {
current, max := js.ethereum.Downloader().Stats() current, max := js.ethereum.Downloader().Stats()
v, _ := call.Otto.ToValue(fmt.Sprintf("%d/%d", current, max))
return js.re.ToVal(fmt.Sprintf("%d/%d", current, max)) return v
} }
func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value { 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() return otto.UndefinedValue()
} }
encoded, _ := rlp.EncodeToBytes(block) 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 { 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() return otto.UndefinedValue()
} }
func (js *jsre) hashrate(otto.FunctionCall) otto.Value { func (js *jsre) hashrate(call otto.FunctionCall) otto.Value {
return js.re.ToVal(js.ethereum.Miner().HashRate()) v, _ := call.Otto.ToValue(js.ethereum.Miner().HashRate())
return v
} }
func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value { 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) fmt.Printf("Could not create the account: %v", err)
return otto.UndefinedValue() 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 { 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 { 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 { 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()) statedb := state.New(block.Root(), js.ethereum.StateDb())
dump := statedb.RawDump() dump := statedb.RawDump()
return js.re.ToVal(dump) v, _ := call.Otto.ToValue(dump)
return v
} }
func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value { 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() return otto.UndefinedValue()
case height = <-wait: 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 { 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 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 { 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) fmt.Println(err)
return otto.UndefinedValue() return otto.UndefinedValue()
} }
return js.re.ToVal(info) v, _ := call.Otto.ToValue(info)
return v
} }
func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value { func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value {

@ -104,7 +104,7 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, interactive boo
func (js *jsre) apiBindings(f xeth.Frontend) { func (js *jsre) apiBindings(f xeth.Frontend) {
xe := xeth.New(js.ethereum, f) xe := xeth.New(js.ethereum, f)
ethApi := rpc.NewEthereumApi(xe) ethApi := rpc.NewEthereumApi(xe)
jeth := rpc.NewJeth(ethApi, js.re.ToVal, js.re) jeth := rpc.NewJeth(ethApi, js.re)
js.re.Set("jeth", struct{}{}) js.re.Set("jeth", struct{}{})
t, _ := js.re.Get("jeth") t, _ := js.re.Get("jeth")

@ -35,6 +35,7 @@ const (
var ( var (
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f"))
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}` 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")) ks := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore"))
am := accounts.NewManager(ks) am := accounts.NewManager(ks)
ethereum, err := eth.New(&eth.Config{ ethereum, err := eth.New(&eth.Config{
NodeKey: testNodeKey,
DataDir: tmp, DataDir: tmp,
AccountManager: am, AccountManager: am,
MaxPeers: 0, MaxPeers: 0,
@ -122,7 +124,7 @@ func TestNodeInfo(t *testing.T) {
} }
defer ethereum.Stop() defer ethereum.Stop()
defer os.RemoveAll(tmp) 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) checkEvalJSON(t, repl, `admin.nodeInfo()`, want)
} }

@ -48,7 +48,7 @@ import _ "net/http/pprof"
const ( const (
ClientIdentifier = "Geth" ClientIdentifier = "Geth"
Version = "0.9.24" Version = "0.9.25"
) )
var ( var (
@ -260,6 +260,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.AutoDAGFlag, utils.AutoDAGFlag,
utils.NATFlag, utils.NATFlag,
utils.NatspecEnabledFlag, utils.NatspecEnabledFlag,
utils.NoDiscoverFlag,
utils.NodeKeyFileFlag, utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag, utils.NodeKeyHexFlag,
utils.RPCEnabledFlag, utils.RPCEnabledFlag,
@ -532,9 +533,9 @@ func importchain(ctx *cli.Context) {
} }
// force database flush // force database flush
ethereum.BlockDb().Close() ethereum.BlockDb().Flush()
ethereum.StateDb().Close() ethereum.StateDb().Flush()
ethereum.ExtraDb().Close() ethereum.ExtraDb().Flush()
fmt.Printf("Import done in %v", time.Since(start)) fmt.Printf("Import done in %v", time.Since(start))
@ -629,9 +630,9 @@ func upgradeDb(ctx *cli.Context) {
} }
// force database flush // force database flush
ethereum.BlockDb().Close() ethereum.BlockDb().Flush()
ethereum.StateDb().Close() ethereum.StateDb().Flush()
ethereum.ExtraDb().Close() ethereum.ExtraDb().Flush()
os.Remove(exportFile) os.Remove(exportFile)

@ -235,6 +235,10 @@ var (
Usage: "NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>)", Usage: "NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>)",
Value: "any", Value: "any",
} }
NoDiscoverFlag = cli.BoolFlag{
Name: "nodiscover",
Usage: "Disables the peer discovery mechanism (manual peer addition)",
}
WhisperEnabledFlag = cli.BoolFlag{ WhisperEnabledFlag = cli.BoolFlag{
Name: "shh", Name: "shh",
Usage: "Enable whisper", Usage: "Enable whisper",
@ -312,6 +316,7 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
Port: ctx.GlobalString(ListenPortFlag.Name), Port: ctx.GlobalString(ListenPortFlag.Name),
NAT: GetNAT(ctx), NAT: GetNAT(ctx),
NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name),
Discovery: !ctx.GlobalBool(NoDiscoverFlag.Name),
NodeKey: GetNodeKey(ctx), NodeKey: GetNodeKey(ctx),
Shh: ctx.GlobalBool(WhisperEnabledFlag.Name), Shh: ctx.GlobalBool(WhisperEnabledFlag.Name),
Dial: true, Dial: true,
@ -320,7 +325,6 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
SolcPath: ctx.GlobalString(SolcPathFlag.Name), SolcPath: ctx.GlobalString(SolcPathFlag.Name),
AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name), AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name),
} }
} }
func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Database) { func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Database) {

@ -34,6 +34,8 @@ var (
"file", // "file", //
"--natspec-dev", // Request to output the contract's Natspec developer documentation. "--natspec-dev", // Request to output the contract's Natspec developer documentation.
"file", "file",
"--add-std",
"1",
} }
) )

@ -21,7 +21,7 @@ import (
const ( const (
// must be bumped when consensus algorithm is changed, this forces the upgradedb // 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) // command to be run (forces the blocks to be imported again using the new algorithm)
BlockChainVersion = 2 BlockChainVersion = 3
) )
var receiptsPre = []byte("receipts-") var receiptsPre = []byte("receipts-")
@ -159,6 +159,9 @@ func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs state.Logs, err
return nil, ParentError(header.ParentHash) return nil, ParentError(header.ParentHash)
} }
parent := sm.bc.GetBlock(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) 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 := new(big.Int).Sub(block.GasLimit, parent.GasLimit)
a.Abs(a) a.Abs(a)
b := new(big.Int).Div(parent.GasLimit, params.GasLimitBoundDivisor) 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) return fmt.Errorf("GasLimit check failed for block %v (%v > %v)", block.GasLimit, a, b)
} }

@ -750,7 +750,7 @@ out:
func blockErr(block *types.Block, err error) { func blockErr(block *types.Block, err error) {
h := block.Header() 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.Error).Infoln(err)
glog.V(logger.Debug).Infoln(block) glog.V(logger.Debug).Infoln(block)
} }

@ -25,6 +25,7 @@ var (
ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value") ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value")
ErrIntrinsicGas = errors.New("Intrinsic gas too low") ErrIntrinsicGas = errors.New("Intrinsic gas too low")
ErrGasLimit = errors.New("Exceeds block gas limit") ErrGasLimit = errors.New("Exceeds block gas limit")
ErrNegativeValue = errors.New("Negative value")
) )
const txPoolQueueSize = 50 const txPoolQueueSize = 50
@ -125,6 +126,10 @@ func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error {
return ErrGasLimit return ErrGasLimit
} }
if tx.Amount.Cmp(common.Big0) < 0 {
return ErrNegativeValue
}
total := new(big.Int).Mul(tx.Price, tx.GasLimit) total := new(big.Int).Mul(tx.Price, tx.GasLimit)
total.Add(total, tx.Value()) total.Add(total, tx.Value())
if pool.currentState().GetBalance(from).Cmp(total) < 0 { if pool.currentState().GetBalance(from).Cmp(total) < 0 {

@ -138,3 +138,17 @@ func TestRemoveTx(t *testing.T) {
t.Error("expected txs to be 0, got", len(pool.txs)) 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)
}
}

@ -72,6 +72,7 @@ type Config struct {
MaxPeers int MaxPeers int
MaxPendingPeers int MaxPendingPeers int
Discovery bool
Port string Port string
// Space-separated list of discovery node URLs // Space-separated list of discovery node URLs
@ -311,6 +312,7 @@ func New(config *Config) (*Ethereum, error) {
Name: config.Name, Name: config.Name,
MaxPeers: config.MaxPeers, MaxPeers: config.MaxPeers,
MaxPendingPeers: config.MaxPendingPeers, MaxPendingPeers: config.MaxPendingPeers,
Discovery: config.Discovery,
Protocols: protocols, Protocols: protocols,
NAT: config.NAT, NAT: config.NAT,
NoDial: !config.Dial, NoDial: !config.Dial,
@ -449,14 +451,10 @@ func (s *Ethereum) Start() error {
ClientString: s.net.Name, ClientString: s.net.Name,
ProtocolVersion: ProtocolVersion, ProtocolVersion: ProtocolVersion,
}) })
err := s.net.Start()
if s.net.MaxPeers > 0 { if err != nil {
err := s.net.Start() return err
if err != nil {
return err
}
} }
// periodically flush databases // periodically flush databases
go s.syncDatabases() go s.syncDatabases()

@ -415,7 +415,7 @@ out:
peer.Demote() peer.Demote()
break 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) glog.Infof("Added %d blocks from: %s\n", len(blockPack.blocks), blockPack.peerId)
} }
// Promote the peer and update it's idle state // Promote the peer and update it's idle state

@ -70,7 +70,6 @@ func (pm *ProtocolManager) processBlocks() error {
// Try to inset the blocks, drop the originating peer if there's an error // Try to inset the blocks, drop the originating peer if there's an error
index, err := pm.chainman.InsertChain(raw) index, err := pm.chainman.InsertChain(raw)
if err != nil { if err != nil {
glog.V(logger.Warn).Infof("Block insertion failed: %v", err)
pm.removePeer(blocks[index].OriginPeer) pm.removePeer(blocks[index].OriginPeer)
pm.downloader.Cancel() pm.downloader.Cancel()
return err return err

@ -19,9 +19,7 @@ It provides some helper functions to
- bind native go objects - bind native go objects
*/ */
type JSRE struct { type JSRE struct {
assetPath string assetPath string
vm *otto.Otto
evalQueue chan *evalReq evalQueue chan *evalReq
stopEventLoop chan bool stopEventLoop chan bool
loopWg sync.WaitGroup loopWg sync.WaitGroup
@ -35,68 +33,37 @@ type jsTimer struct {
call otto.FunctionCall call otto.FunctionCall
} }
// evalResult is a structure to store the result of any serialized vm execution // evalReq is a serialized vm execution request processed by runEventLoop.
type evalResult struct {
result otto.Value
err error
}
// evalReq is a serialized vm execution request put in evalQueue and processed by runEventLoop
type evalReq struct { type evalReq struct {
fn func(res *evalResult) fn func(vm *otto.Otto)
done chan bool done chan bool
res evalResult
} }
// runtime must be stopped with Stop() after use and cannot be used after stopping // runtime must be stopped with Stop() after use and cannot be used after stopping
func New(assetPath string) *JSRE { func New(assetPath string) *JSRE {
re := &JSRE{ re := &JSRE{
assetPath: assetPath, assetPath: assetPath,
vm: otto.New(), 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) re.loopWg.Add(1)
go re.runEventLoop() go re.runEventLoop()
re.Compile("pp.js", pp_js) // load prettyprint func definition
re.Set("loadScript", re.loadScript)
return re return re
} }
// this function runs a piece of JS code either in a serialized way (when useEQ is true) or instantly, circumventing the evalQueue // This function runs the main event loop from a goroutine that is started
func (self *JSRE) run(src interface{}, useEQ bool) (value otto.Value, err error) { // when JSRE is created. Use Stop() before exiting to properly stop it.
if useEQ { // The event loop processes vm access requests from the evalQueue in a
done := make(chan bool) // serialized way and calls timer callback functions at the appropriate time.
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)
}
}
/* // Exported functions always access the vm through the event queue. You can
This function runs the main event loop from a goroutine that is started // call the functions of the otto vm directly to circumvent the queue. These
when JSRE is created. Use Stop() before exiting to properly stop it. // functions should be used if and only if running a routine that was already
The event loop processes vm access requests from the evalQueue in a // called from JS through an RPC call.
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.
*/
func (self *JSRE) runEventLoop() { func (self *JSRE) runEventLoop() {
vm := otto.New()
registry := map[*jsTimer]*jsTimer{} registry := map[*jsTimer]*jsTimer{}
ready := make(chan *jsTimer) ready := make(chan *jsTimer)
@ -143,10 +110,10 @@ func (self *JSRE) runEventLoop() {
} }
return otto.UndefinedValue() return otto.UndefinedValue()
} }
self.vm.Set("setTimeout", setTimeout) vm.Set("setTimeout", setTimeout)
self.vm.Set("setInterval", setInterval) vm.Set("setInterval", setInterval)
self.vm.Set("clearTimeout", clearTimeout) vm.Set("clearTimeout", clearTimeout)
self.vm.Set("clearInterval", clearTimeout) vm.Set("clearInterval", clearTimeout)
var waitForCallbacks bool var waitForCallbacks bool
@ -166,8 +133,7 @@ loop:
arguments = make([]interface{}, 1) arguments = make([]interface{}, 1)
} }
arguments[0] = timer.call.ArgumentList[0] 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 { if err != nil {
fmt.Println("js error:", err, arguments) fmt.Println("js error:", err, arguments)
} }
@ -179,10 +145,10 @@ loop:
break loop break loop
} }
} }
case evalReq := <-self.evalQueue: case req := <-self.evalQueue:
// run the code, send the result back // run the code, send the result back
evalReq.fn(&evalReq.res) req.fn(vm)
close(evalReq.done) close(req.done)
if waitForCallbacks && (len(registry) == 0) { if waitForCallbacks && (len(registry) == 0) {
break loop break loop
} }
@ -201,6 +167,14 @@ loop:
self.loopWg.Done() 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 // stops the event loop before exit, optionally waits for all timers to expire
func (self *JSRE) Stop(waitForCallbacks bool) { func (self *JSRE) Stop(waitForCallbacks bool) {
self.stopEventLoop <- waitForCallbacks self.stopEventLoop <- waitForCallbacks
@ -210,119 +184,78 @@ func (self *JSRE) Stop(waitForCallbacks bool) {
// Exec(file) loads and runs the contents of a file // Exec(file) loads and runs the contents of a file
// if a relative path is given, the jsre's assetPath is used // if a relative path is given, the jsre's assetPath is used
func (self *JSRE) Exec(file string) error { func (self *JSRE) Exec(file string) error {
return self.exec(common.AbsolutePath(self.assetPath, file), true) code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file))
}
// 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)
if err != nil { if err != nil {
return err return err
} }
_, err = self.run(code, useEQ) self.do(func(vm *otto.Otto) { _, err = vm.Run(code) })
return err return err
} }
// assigns value v to a variable in the JS environment // Bind assigns value v to a variable in the JS environment
func (self *JSRE) Bind(name string, v interface{}) (err error) { // This method is deprecated, use Set.
self.Set(name, v) func (self *JSRE) Bind(name string, v interface{}) error {
return return self.Set(name, v)
} }
// runs a piece of JS code // Run runs a piece of JS code.
func (self *JSRE) Run(code string) (otto.Value, error) { func (self *JSRE) Run(code string) (v otto.Value, err error) {
return self.run(code, true) self.do(func(vm *otto.Otto) { v, err = vm.Run(code) })
return v, err
} }
// returns the value of a variable in the JS environment // Get returns the value of a variable in the JS environment.
func (self *JSRE) Get(ns string) (otto.Value, error) { func (self *JSRE) Get(ns string) (v otto.Value, err error) {
done := make(chan bool) self.do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
req := &evalReq{ return v, err
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
} }
// assigns value v to a variable in the JS environment // Set assigns value v to a variable in the JS environment.
func (self *JSRE) Set(ns string, v interface{}) error { func (self *JSRE) Set(ns string, v interface{}) (err error) {
done := make(chan bool) self.do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
req := &evalReq{ return err
fn: func(res *evalResult) {
res.err = self.vm.Set(ns, v)
},
done: done,
}
self.evalQueue <- req
<-done
return req.res.err
} }
/* // loadScript executes a JS script from inside the currently executing JS code.
Executes a JS script from inside the currently executing JS code.
Should only be called from inside an RPC routine.
*/
func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value { func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
file, err := call.Argument(0).ToString() file, err := call.Argument(0).ToString()
if err != nil { if err != nil {
// TODO: throw exception
return otto.FalseValue() return otto.FalseValue()
} }
if err := self.execWithoutEQ(file); err != nil { // loadScript is only called from inside js file = common.AbsolutePath(self.assetPath, file)
source, err := ioutil.ReadFile(file)
if err != nil {
// TODO: throw exception
return otto.FalseValue()
}
if _, err := compileAndRun(call.Otto, file, source); err != nil {
// TODO: throw exception
fmt.Println("err:", err) fmt.Println("err:", err)
return otto.FalseValue() return otto.FalseValue()
} }
// TODO: return evaluation result
return otto.TrueValue() 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) { func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
var method otto.Value var method otto.Value
v, err = self.ToValue(v) self.do(func(vm *otto.Otto) {
if err != nil { val, err = vm.ToValue(v)
return if err != nil {
} return
method, err = self.vm.Get("prettyPrint") }
if err != nil { method, err = vm.Get("prettyPrint")
return if err != nil {
} return
return method.Call(method, v) }
val, err = method.Call(method, val)
})
return val, err
} }
// creates an otto value from a go type (serialized version) // Eval evaluates JS function and returns result in a pretty printed string format.
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
}
// evaluates JS function and returns result in a pretty printed string format
func (self *JSRE) Eval(code string) (s string, err error) { func (self *JSRE) Eval(code string) (s string, err error) {
var val otto.Value var val otto.Value
val, err = self.Run(code) 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 return fmt.Sprintf("%v", val), nil
} }
// compiles and then runs a piece of JS code // Compile compiles and then runs a piece of JS code.
func (self *JSRE) Compile(fn string, src interface{}) error { func (self *JSRE) Compile(filename string, src interface{}) (err error) {
script, err := self.vm.Compile(fn, src) self.do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
if err != nil { return err
return err }
}
self.run(script, true) func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
return nil script, err := vm.Compile(filename, src)
if err != nil {
return otto.Value{}, err
}
return vm.Run(script)
} }

@ -1,16 +1,15 @@
package jsre package jsre
import ( import (
"github.com/robertkrimen/otto"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/robertkrimen/otto"
) )
type testNativeObjectBinding struct { type testNativeObjectBinding struct{}
toVal func(interface{}) otto.Value
}
type msg struct { type msg struct {
Msg string Msg string
@ -21,7 +20,8 @@ func (no *testNativeObjectBinding) TestMethod(call otto.FunctionCall) otto.Value
if err != nil { if err != nil {
return otto.UndefinedValue() return otto.UndefinedValue()
} }
return no.toVal(&msg{m}) v, _ := call.Otto.ToValue(&msg{m})
return v
} }
func TestExec(t *testing.T) { func TestExec(t *testing.T) {
@ -74,7 +74,7 @@ func TestNatto(t *testing.T) {
func TestBind(t *testing.T) { func TestBind(t *testing.T) {
jsre := New("/tmp") jsre := New("/tmp")
jsre.Bind("no", &testNativeObjectBinding{jsre.ToVal}) jsre.Bind("no", &testNativeObjectBinding{})
val, err := jsre.Run(`no.TestMethod("testMsg")`) val, err := jsre.Run(`no.TestMethod("testMsg")`)
if err != nil { if err != nil {

@ -44,9 +44,10 @@ var (
nodeDBVersionKey = []byte("version") // Version of the database to flush if changes nodeDBVersionKey = []byte("version") // Version of the database to flush if changes
nodeDBItemPrefix = []byte("n:") // Identifier to prefix node entries with nodeDBItemPrefix = []byte("n:") // Identifier to prefix node entries with
nodeDBDiscoverRoot = ":discover" nodeDBDiscoverRoot = ":discover"
nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping" nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping"
nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong" nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong"
nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail"
) )
// newNodeDB creates a new node database for storing and retrieving infos about // 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()) 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 // querySeeds retrieves a batch of nodes to be used as potential seed servers
// during bootstrapping the node into the network. // during bootstrapping the node into the network.
// //

@ -93,6 +93,7 @@ func TestNodeDBFetchStore(t *testing.T) {
30303, 30303,
) )
inst := time.Now() inst := time.Now()
num := 314
db, _ := newNodeDB("", Version, NodeID{}) db, _ := newNodeDB("", Version, NodeID{})
defer db.close() defer db.close()
@ -117,6 +118,16 @@ func TestNodeDBFetchStore(t *testing.T) {
if stored := db.lastPong(node.ID); stored.Unix() != inst.Unix() { if stored := db.lastPong(node.ID); stored.Unix() != inst.Unix() {
t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) 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 // Check fetch/store operations on an actual node object
if stored := db.node(node.ID); stored != nil { if stored := db.node(node.ID); stored != nil {
t.Errorf("node: non-existing object: %v", stored) t.Errorf("node: non-existing object: %v", stored)

@ -27,6 +27,7 @@ const (
nBuckets = hashBits + 1 // Number of buckets nBuckets = hashBits + 1 // Number of buckets
maxBondingPingPongs = 16 maxBondingPingPongs = 16
maxFindnodeFailures = 5
) )
type Table struct { type Table struct {
@ -190,6 +191,12 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
result := tab.closest(target, bucketSize) result := tab.closest(target, bucketSize)
tab.mutex.Unlock() tab.mutex.Unlock()
// If the result set is empty, all nodes were dropped, refresh
if len(result.entries) == 0 {
tab.refresh()
return nil
}
for { for {
// ask the alpha closest nodes that we haven't asked yet // ask the alpha closest nodes that we haven't asked yet
for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { 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 asked[n.ID] = true
pendingQueries++ pendingQueries++
go func() { 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) reply <- tab.bondall(r)
}() }()
} }
@ -219,30 +238,53 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
return result.entries 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() { func (tab *Table) refresh() {
// The Kademlia paper specifies that the bucket refresh should seed := true
// perform a refresh in the least recently used bucket. We cannot
// adhere to this because the findnode target is a 512bit value // If the discovery table is empty, seed with previously known nodes
// (not hash-sized) and it is not easily possible to generate a tab.mutex.Lock()
// sha3 preimage that falls into a chosen bucket. for _, bucket := range tab.buckets {
// if len(bucket.entries) > 0 {
// We perform a lookup with a random target instead. seed = false
var target NodeID break
rand.Read(target[:]) }
result := tab.Lookup(target) }
if len(result) == 0 { 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 // Pick a batch of previously know seeds to lookup with
seeds := tab.db.querySeeds(10) seeds := tab.db.querySeeds(10)
for _, seed := range seeds { for _, seed := range seeds {
glog.V(logger.Debug).Infoln("Seeding network with", seed) glog.V(logger.Debug).Infoln("Seeding network with", seed)
} }
// Bootstrap the table with a self lookup nodes := append(tab.nursery, seeds...)
all := tab.bondall(append(tab.nursery, seeds...))
tab.mutex.Lock() // Bond with all the seed nodes (will pingpong only if failed recently)
tab.add(all) bonded := tab.bondall(nodes)
tab.mutex.Unlock() if len(bonded) > 0 {
tab.Lookup(tab.self.ID) tab.Lookup(tab.self.ID)
}
// TODO: the Kademlia paper says that we're supposed to perform // TODO: the Kademlia paper says that we're supposed to perform
// random lookups in all buckets further away than our closest neighbor. // 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 // If pinged is true, the remote node has just pinged us and one half
// of the process can be skipped. // of the process can be skipped.
func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) (*Node, error) { func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) (*Node, error) {
var n *Node // Retrieve a previously known node and any recent findnode failures
if n = tab.db.node(id); n == nil { 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() tab.bondmu.Lock()
w := tab.bonding[id] w := tab.bonding[id]
if w != nil { if w != nil {
@ -325,18 +375,24 @@ func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16
delete(tab.bonding, id) delete(tab.bonding, id)
tab.bondmu.Unlock() tab.bondmu.Unlock()
} }
n = w.n // Retrieve the bonding results
if w.err != nil { result = w.err
return nil, w.err if result == nil {
node = w.n
} }
} }
tab.mutex.Lock() // Even if bonding temporarily failed, give the node a chance
defer tab.mutex.Unlock() if node != nil {
b := tab.buckets[logdist(tab.self.sha, n.sha)] tab.mutex.Lock()
if !b.bump(n) { defer tab.mutex.Unlock()
tab.pingreplace(n, b)
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) { 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 { func (b *bucket) bump(n *Node) bool {
for i := range b.entries { for i := range b.entries {
if b.entries[i].ID == n.ID { if b.entries[i].ID == n.ID {

@ -55,6 +55,10 @@ type Server struct {
// Zero defaults to preset values. // Zero defaults to preset values.
MaxPendingPeers int 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. // Name sets the node name of this server.
// Use common.MakeName to create a name that follows existing conventions. // Use common.MakeName to create a name that follows existing conventions.
Name string Name string
@ -237,9 +241,26 @@ func (srv *Server) AddPeer(node *discover.Node) {
func (srv *Server) Self() *discover.Node { func (srv *Server) Self() *discover.Node {
srv.lock.Lock() srv.lock.Lock()
defer srv.lock.Unlock() defer srv.lock.Unlock()
// If the server's not running, return an empty node
if !srv.running { if !srv.running {
return &discover.Node{IP: net.ParseIP("0.0.0.0")} 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() return srv.ntab.Self()
} }
@ -275,9 +296,6 @@ func (srv *Server) Start() (err error) {
if srv.PrivateKey == nil { if srv.PrivateKey == nil {
return fmt.Errorf("Server.PrivateKey must be set to a non-nil key") 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 { if srv.newTransport == nil {
srv.newTransport = newRLPX srv.newTransport = newRLPX
} }
@ -293,15 +311,22 @@ func (srv *Server) Start() (err error) {
srv.peerOpDone = make(chan struct{}) srv.peerOpDone = make(chan struct{})
// node table // node table
ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT, srv.NodeDatabase) if srv.Discovery {
if err != nil { ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT, srv.NodeDatabase)
return err 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 // 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 { for _, p := range srv.Protocols {
srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap()) 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. // 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. // Disconnect all peers.
for _, p := range peers { for _, p := range peers {
p.Disconnect(DiscQuitting) p.Disconnect(DiscQuitting)
@ -489,7 +516,7 @@ func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn)
return DiscTooManyPeers return DiscTooManyPeers
case peers[c.id] != nil: case peers[c.id] != nil:
return DiscAlreadyConnected return DiscAlreadyConnected
case c.id == srv.ntab.Self().ID: case c.id == srv.Self().ID:
return DiscSelf return DiscSelf
default: default:
return nil return nil

@ -3,18 +3,18 @@ package rpc
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/jsre" "github.com/ethereum/go-ethereum/jsre"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
) )
type Jeth struct { type Jeth struct {
ethApi *EthereumApi ethApi *EthereumApi
toVal func(interface{}) otto.Value
re *jsre.JSRE re *jsre.JSRE
} }
func NewJeth(ethApi *EthereumApi, toVal func(interface{}) otto.Value, re *jsre.JSRE) *Jeth { func NewJeth(ethApi *EthereumApi, re *jsre.JSRE) *Jeth {
return &Jeth{ethApi, toVal, re} return &Jeth{ethApi, re}
} }
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) { func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {