diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go index 9be072e299..d398aa5f80 100644 --- a/cmd/geth/admin.go +++ b/cmd/geth/admin.go @@ -259,8 +259,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() - v, _ := call.Otto.ToValue(fmt.Sprintf("%d/%d", current, max)) + pending, cached := js.ethereum.Downloader().Stats() + v, _ := call.Otto.ToValue(map[string]interface{}{"pending": pending, "cached": cached}) return v } diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 54fcf3ba4c..af9b9f7424 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -22,6 +22,7 @@ import ( "fmt" "math/big" "os" + "os/signal" "path/filepath" "strings" @@ -49,7 +50,8 @@ type dumbterm struct{ r *bufio.Reader } func (r dumbterm) Prompt(p string) (string, error) { fmt.Print(p) - return r.r.ReadString('\n') + line, err := r.r.ReadString('\n') + return strings.TrimSuffix(line, "\n"), err } func (r dumbterm) PasswordPrompt(p string) (string, error) { @@ -190,30 +192,52 @@ func (self *jsre) exec(filename string) error { } func (self *jsre) interactive() { - for { - input, err := self.Prompt(self.ps1) - if err != nil { - break - } - if input == "" { - continue - } - str += input + "\n" - self.setIndent() - if indentCount <= 0 { - if input == "exit" { - break + // Read input lines. + prompt := make(chan string) + inputln := make(chan string) + go func() { + defer close(inputln) + for { + line, err := self.Prompt(<-prompt) + if err != nil { + return + } + inputln <- line + } + }() + // Wait for Ctrl-C, too. + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt) + + defer func() { + if self.atexit != nil { + self.atexit() + } + self.re.Stop(false) + }() + for { + prompt <- self.ps1 + select { + case <-sig: + fmt.Println("caught interrupt, exiting") + return + case input, ok := <-inputln: + if !ok || indentCount <= 0 && input == "exit" { + return + } + if input == "" { + continue + } + str += input + "\n" + self.setIndent() + if indentCount <= 0 { + hist := str[:len(str)-1] + self.AppendHistory(hist) + self.parseInput(str) + str = "" } - hist := str[:len(str)-1] - self.AppendHistory(hist) - self.parseInput(str) - str = "" } } - if self.atexit != nil { - self.atexit() - } - self.re.Stop(false) } func (self *jsre) withHistory(op func(*os.File)) { diff --git a/common/db.go b/common/db.go index ae13c75573..c12a2cfb03 100644 --- a/common/db.go +++ b/common/db.go @@ -5,7 +5,6 @@ type Database interface { Put(key []byte, value []byte) Get(key []byte) ([]byte, error) Delete(key []byte) error - LastKnownTD() []byte Close() Flush() error } diff --git a/core/block_processor.go b/core/block_processor.go index e808c03da0..6cd1c8aa35 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -285,9 +285,8 @@ func (self *BlockProcessor) GetBlockReceipts(bhash common.Hash) (receipts types. } -// Validates the current block. Returns an error if the block was invalid, -// an uncle or anything that isn't on the current block chain. -// Validation validates easy over difficult (dagger takes longer time = difficult) +// See YP section 4.3.4. "Block Header Validity" +// Validates a block. Returns an error if the block is invalid. func (sm *BlockProcessor) ValidateHeader(block, parent *types.Header, checkPow bool) error { if big.NewInt(int64(len(block.Extra))).Cmp(params.MaximumExtraDataSize) == 1 { return fmt.Errorf("Block extra data too long (%d)", len(block.Extra)) @@ -298,16 +297,14 @@ func (sm *BlockProcessor) ValidateHeader(block, parent *types.Header, checkPow b return fmt.Errorf("Difficulty check failed for block %v, %v", block.Difficulty, expd) } - // block.gasLimit - parent.gasLimit <= parent.gasLimit / GasLimitBoundDivisor 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) } - // Allow future blocks up to 10 seconds - if int64(block.Time) > time.Now().Unix()+4 { + if int64(block.Time) > time.Now().Unix() { return BlockFutureErr } diff --git a/core/chain_manager.go b/core/chain_manager.go index ec479db25c..ee73145c10 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -69,6 +69,7 @@ func CalcGasLimit(parent *types.Block) *big.Int { gl := new(big.Int).Sub(parent.GasLimit(), decay) gl = gl.Add(gl, contrib) + gl = gl.Add(gl, big.NewInt(1)) gl = common.BigMax(gl, params.MinGasLimit) if gl.Cmp(params.GenesisGasLimit) < 0 { diff --git a/eth/handler.go b/eth/handler.go index 777a9c7c0e..8092a5f714 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -93,14 +93,22 @@ func NewProtocolManager(protocolVersion, networkId int, mux *event.TypeMux, txpo } func (pm *ProtocolManager) removePeer(id string) { - // Unregister the peer from the downloader - pm.downloader.UnregisterPeer(id) + // Short circuit if the peer was already removed + peer := pm.peers.Peer(id) + if peer == nil { + return + } + glog.V(logger.Debug).Infoln("Removing peer", id) - // Remove the peer from the Ethereum peer set too - glog.V(logger.Detail).Infoln("Removing peer", id) + // Unregister the peer from the downloader and Ethereum peer set + pm.downloader.UnregisterPeer(id) if err := pm.peers.Unregister(id); err != nil { glog.V(logger.Error).Infoln("Removal failed:", err) } + // Hard disconnect at the networking layer + if peer != nil { + peer.Peer.Disconnect(p2p.DiscUselessPeer) + } } func (pm *ProtocolManager) Start() { diff --git a/ethdb/database.go b/ethdb/database.go index 04ea427053..f605036c40 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -1,8 +1,6 @@ package ethdb import ( - "sync" - "github.com/ethereum/go-ethereum/compression/rle" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -15,14 +13,10 @@ import ( var OpenFileLimit = 64 type LDBDatabase struct { + // filename for reporting fn string - - mu sync.Mutex + // LevelDB instance db *leveldb.DB - - queue map[string][]byte - - quit chan struct{} } // NewLDBDatabase returns a LevelDB wrapped object. LDBDatabase does not persist data by @@ -40,85 +34,39 @@ func NewLDBDatabase(file string) (*LDBDatabase, error) { return nil, err } database := &LDBDatabase{ - fn: file, - db: db, - quit: make(chan struct{}), + fn: file, + db: db, } - database.makeQueue() return database, nil } -func (self *LDBDatabase) makeQueue() { - self.queue = make(map[string][]byte) -} - // Put puts the given key / value to the queue func (self *LDBDatabase) Put(key []byte, value []byte) { - self.mu.Lock() - defer self.mu.Unlock() - - self.queue[string(key)] = value + self.db.Put(key, rle.Compress(value), nil) } // Get returns the given key if it's present. func (self *LDBDatabase) Get(key []byte) ([]byte, error) { - self.mu.Lock() - defer self.mu.Unlock() - - // Check queue first - if dat, ok := self.queue[string(key)]; ok { - return dat, nil - } - dat, err := self.db.Get(key, nil) if err != nil { return nil, err } - return rle.Decompress(dat) } // Delete deletes the key from the queue and database func (self *LDBDatabase) Delete(key []byte) error { - self.mu.Lock() - defer self.mu.Unlock() - - // make sure it's not in the queue - delete(self.queue, string(key)) - return self.db.Delete(key, nil) } -func (self *LDBDatabase) LastKnownTD() []byte { - data, _ := self.Get([]byte("LTD")) - - if len(data) == 0 { - data = []byte{0x0} - } - - return data -} - func (self *LDBDatabase) NewIterator() iterator.Iterator { return self.db.NewIterator(nil, nil) } // Flush flushes out the queue to leveldb func (self *LDBDatabase) Flush() error { - self.mu.Lock() - defer self.mu.Unlock() - - batch := new(leveldb.Batch) - - for key, value := range self.queue { - batch.Put([]byte(key), rle.Compress(value)) - } - self.makeQueue() // reset the queue - - glog.V(logger.Detail).Infoln("Flush database: ", self.fn) - - return self.db.Write(batch, nil) + return nil } func (self *LDBDatabase) Write(batch *leveldb.Batch) error { diff --git a/miner/worker.go b/miner/worker.go index 5e4ff7510f..bc69551696 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -38,6 +38,13 @@ type Agent interface { GetHashRate() int64 } +const miningLogAtDepth = 5 + +type uint64RingBuffer struct { + ints []uint64 //array of all integers in buffer + next int //where is the next insertion? assert 0 <= next < len(ints) +} + // environment is the workers current environment and holds // all of the current state information type environment struct { @@ -54,6 +61,7 @@ type environment struct { lowGasTransactors *set.Set ownedAccounts *set.Set lowGasTxs types.Transactions + localMinedBlocks *uint64RingBuffer // the most recent block numbers that were mined locally (used to check block inclusion) } // env returns a new environment for the current cycle @@ -209,6 +217,18 @@ out: events.Unsubscribe() } +func newLocalMinedBlock(blockNumber uint64, prevMinedBlocks *uint64RingBuffer) (minedBlocks *uint64RingBuffer) { + if prevMinedBlocks == nil { + minedBlocks = &uint64RingBuffer{next: 0, ints: make([]uint64, miningLogAtDepth+1)} + } else { + minedBlocks = prevMinedBlocks + } + + minedBlocks.ints[minedBlocks.next] = blockNumber + minedBlocks.next = (minedBlocks.next + 1) % len(minedBlocks.ints) + return minedBlocks +} + func (self *worker) wait() { for { for block := range self.recv { @@ -224,13 +244,16 @@ func (self *worker) wait() { } self.mux.Post(core.NewMinedBlockEvent{block}) - var stale string + var stale, confirm string canonBlock := self.chain.GetBlockByNumber(block.NumberU64()) if canonBlock != nil && canonBlock.Hash() != block.Hash() { - stale = "stale-" + stale = "stale " + } else { + confirm = "Wait 5 blocks for confirmation" + self.current.localMinedBlocks = newLocalMinedBlock(block.Number().Uint64(), self.current.localMinedBlocks) } - glog.V(logger.Info).Infof("🔨 Mined %sblock #%v (%x)", stale, block.Number(), block.Hash().Bytes()[:4]) + glog.V(logger.Info).Infof("🔨 Mined %sblock (#%v / %x). %s", stale, block.Number(), block.Hash().Bytes()[:4], confirm) jsonlogger.LogJson(&logger.EthMinerNewBlock{ BlockHash: block.Hash().Hex(), @@ -265,8 +288,10 @@ func (self *worker) push() { func (self *worker) makeCurrent() { block := self.chain.NewBlock(self.coinbase) - if block.Time() == self.chain.CurrentBlock().Time() { - block.Header().Time++ + parent := self.chain.GetBlock(block.ParentHash()) + + if block.Time() <= parent.Time() { + block.Header().Time = parent.Header().Time + 1 } block.Header().Extra = self.extra @@ -286,8 +311,10 @@ func (self *worker) makeCurrent() { current.ignoredTransactors = set.New() current.lowGasTransactors = set.New() current.ownedAccounts = accountAddressesSet(accounts) + if self.current != nil { + current.localMinedBlocks = self.current.localMinedBlocks + } - parent := self.chain.GetBlock(current.block.ParentHash()) current.coinbase.SetGasPool(core.CalcGasLimit(parent)) self.current = current @@ -304,6 +331,38 @@ func (w *worker) setGasPrice(p *big.Int) { w.mux.Post(core.GasPriceChanged{w.gasPrice}) } +func (self *worker) isBlockLocallyMined(deepBlockNum uint64) bool { + //Did this instance mine a block at {deepBlockNum} ? + var isLocal = false + for idx, blockNum := range self.current.localMinedBlocks.ints { + if deepBlockNum == blockNum { + isLocal = true + self.current.localMinedBlocks.ints[idx] = 0 //prevent showing duplicate logs + break + } + } + //Short-circuit on false, because the previous and following tests must both be true + if !isLocal { + return false + } + + //Does the block at {deepBlockNum} send earnings to my coinbase? + var block = self.chain.GetBlockByNumber(deepBlockNum) + return block.Header().Coinbase == self.coinbase +} + +func (self *worker) logLocalMinedBlocks(previous *environment) { + if previous != nil && self.current.localMinedBlocks != nil { + nextBlockNum := self.current.block.Number().Uint64() + for checkBlockNum := previous.block.Number().Uint64(); checkBlockNum < nextBlockNum; checkBlockNum++ { + inspectBlockNum := checkBlockNum - miningLogAtDepth + if self.isBlockLocallyMined(inspectBlockNum) { + glog.V(logger.Info).Infof("🔨 🔗 Mined %d blocks back: block #%v", miningLogAtDepth, inspectBlockNum) + } + } + } +} + func (self *worker) commitNewWork() { self.mu.Lock() defer self.mu.Unlock() @@ -312,6 +371,7 @@ func (self *worker) commitNewWork() { self.currentMu.Lock() defer self.currentMu.Unlock() + previous := self.current self.makeCurrent() current := self.current @@ -347,6 +407,7 @@ func (self *worker) commitNewWork() { // We only care about logging if we're actually mining if atomic.LoadInt32(&self.mining) == 1 { glog.V(logger.Info).Infof("commit new work on block %v with %d txs & %d uncles\n", current.block.Number(), current.tcount, len(uncles)) + self.logLocalMinedBlocks(previous) } for _, hash := range badUncles {