diff options
author | obscuren <geffobscura@gmail.com> | 2015-05-27 08:07:03 +0800 |
---|---|---|
committer | obscuren <geffobscura@gmail.com> | 2015-05-27 08:07:03 +0800 |
commit | 70867904a0255bd044851585a9ad2dc34391ced2 (patch) | |
tree | 9c5dfc6a9b8944c37f874a81cc9f8fd3d697ad4e | |
parent | ceea1a7051ddc7d2660117204aeb3392b7cf8314 (diff) | |
parent | 2c532a7255919bc09e01cca6866bfc15682509a3 (diff) | |
download | go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.tar go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.tar.gz go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.tar.bz2 go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.tar.lz go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.tar.xz go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.tar.zst go-tangerine-70867904a0255bd044851585a9ad2dc34391ced2.zip |
Merge branch 'release/0.9.25'v0.9.25
-rw-r--r-- | cmd/geth/admin.go | 70 | ||||
-rw-r--r-- | cmd/geth/js.go | 2 | ||||
-rw-r--r-- | cmd/geth/js_test.go | 4 | ||||
-rw-r--r-- | cmd/geth/main.go | 15 | ||||
-rw-r--r-- | cmd/utils/flags.go | 6 | ||||
-rw-r--r-- | common/compiler/solidity.go | 2 | ||||
-rw-r--r-- | core/block_processor.go | 7 | ||||
-rw-r--r-- | core/chain_manager.go | 2 | ||||
-rw-r--r-- | core/transaction_pool.go | 5 | ||||
-rw-r--r-- | core/transaction_pool_test.go | 14 | ||||
-rw-r--r-- | eth/backend.go | 12 | ||||
-rw-r--r-- | eth/downloader/downloader.go | 2 | ||||
-rw-r--r-- | eth/sync.go | 1 | ||||
-rw-r--r-- | jsre/jsre.go | 235 | ||||
-rw-r--r-- | jsre/jsre_test.go | 12 | ||||
-rw-r--r-- | p2p/discover/database.go | 17 | ||||
-rw-r--r-- | p2p/discover/database_test.go | 11 | ||||
-rw-r--r-- | p2p/discover/table.go | 131 | ||||
-rw-r--r-- | p2p/server.go | 49 | ||||
-rw-r--r-- | rpc/jeth.go | 6 |
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(ð.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) { |