diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go index 523b7c4065..4c8f110e43 100644 --- a/cmd/geth/admin.go +++ b/cmd/geth/admin.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" @@ -73,6 +74,9 @@ func (js *jsre) adminBindings() { miner.Set("hashrate", js.hashrate) miner.Set("setExtra", js.setExtra) miner.Set("setGasPrice", js.setGasPrice) + miner.Set("startAutoDAG", js.startAutoDAG) + miner.Set("stopAutoDAG", js.stopAutoDAG) + miner.Set("makeDAG", js.makeDAG) admin.Set("debug", struct{}{}) t, _ = admin.Get("debug") @@ -278,6 +282,30 @@ func (js *jsre) hashrate(otto.FunctionCall) otto.Value { return js.re.ToVal(js.ethereum.Miner().HashRate()) } +func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value { + blockNumber, err := call.Argument(1).ToInteger() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + err = ethash.MakeDAG(uint64(blockNumber), "") + if err != nil { + return otto.FalseValue() + } + return otto.TrueValue() +} + +func (js *jsre) startAutoDAG(otto.FunctionCall) otto.Value { + js.ethereum.StartAutoDAG() + return otto.TrueValue() +} + +func (js *jsre) stopAutoDAG(otto.FunctionCall) otto.Value { + js.ethereum.StopAutoDAG() + return otto.TrueValue() +} + func (js *jsre) backtrace(call otto.FunctionCall) otto.Value { tracestr, err := call.Argument(0).ToString() if err != nil { @@ -316,6 +344,9 @@ func (js *jsre) startMining(call otto.FunctionCall) otto.Value { threads = int64(js.ethereum.MinerThreads) } + // switch on DAG autogeneration when miner starts + js.ethereum.StartAutoDAG() + err = js.ethereum.StartMining(int(threads)) if err != nil { fmt.Println(err) @@ -327,6 +358,7 @@ func (js *jsre) startMining(call otto.FunctionCall) otto.Value { func (js *jsre) stopMining(call otto.FunctionCall) otto.Value { js.ethereum.StopMining() + js.ethereum.StopAutoDAG() return otto.TrueValue() } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index ba253bbebb..6d345a18bf 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -117,7 +117,7 @@ passwordfile as argument containing the wallet password in plaintext. Manage accounts lets you create new accounts, list all existing accounts, import a private key into a new account. -'account help' shows a list of subcommands or help for one subcommand. +' help' shows a list of subcommands or help for one subcommand. It supports interactive mode, when you are prompted for password as well as non-interactive mode where passwords are supplied via a given password file. @@ -257,6 +257,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.GasPriceFlag, utils.MinerThreadsFlag, utils.MiningEnabledFlag, + utils.AutoDAGFlag, utils.NATFlag, utils.NatspecEnabledFlag, utils.NodeKeyFileFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2766e75171..cb774aa5bf 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -112,6 +112,10 @@ var ( Name: "mine", Usage: "Enable mining", } + AutoDAGFlag = cli.BoolFlag{ + Name: "autodag", + Usage: "Enable automatic DAG pregeneration", + } EtherbaseFlag = cli.StringFlag{ Name: "etherbase", Usage: "Public address for block mining rewards. By default the address of your primary account is used", @@ -314,6 +318,7 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { BootNodes: ctx.GlobalString(BootnodesFlag.Name), GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)), SolcPath: ctx.GlobalString(SolcPathFlag.Name), + AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name), } } diff --git a/eth/backend.go b/eth/backend.go index 69504fd941..938071fc7e 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -31,6 +31,14 @@ import ( "github.com/ethereum/go-ethereum/whisper" ) +const ( + epochLength = 30000 + ethashRevision = 23 + + autoDAGcheckInterval = 10 * time.Hour + autoDAGepochHeight = epochLength / 2 +) + var ( jsonlogger = logger.NewJsonLogger() @@ -60,6 +68,7 @@ type Config struct { LogJSON string VmDebug bool NatSpec bool + AutoDAG bool MaxPeers int MaxPendingPeers int @@ -197,6 +206,8 @@ type Ethereum struct { MinerThreads int NatSpec bool DataDir string + AutoDAG bool + autodagquit chan bool etherbase common.Address clientVersion string ethVersionId int @@ -269,6 +280,7 @@ func New(config *Config) (*Ethereum, error) { NatSpec: config.NatSpec, MinerThreads: config.MinerThreads, SolcPath: config.SolcPath, + AutoDAG: config.AutoDAG, } eth.pow = ethash.New() @@ -448,6 +460,10 @@ func (s *Ethereum) Start() error { // periodically flush databases go s.syncDatabases() + if s.AutoDAG { + s.StartAutoDAG() + } + // Start services go s.txPool.Start() s.protocolManager.Start() @@ -526,6 +542,7 @@ func (s *Ethereum) Stop() { if s.whisper != nil { s.whisper.Stop() } + s.StopAutoDAG() glog.V(logger.Info).Infoln("Server stopped") close(s.shutdownChan) @@ -559,6 +576,77 @@ func (self *Ethereum) syncAccounts(tx *types.Transaction) { } } +// StartAutoDAG() spawns a go routine that checks the DAG every autoDAGcheckInterval +// by default that is 10 times per epoch +// in epoch n, if we past autoDAGepochHeight within-epoch blocks, +// it calls ethash.MakeDAG to pregenerate the DAG for the next epoch n+1 +// if it does not exist yet as well as remove the DAG for epoch n-1 +// the loop quits if autodagquit channel is closed, it can safely restart and +// stop any number of times. +// For any more sophisticated pattern of DAG generation, use CLI subcommand +// makedag +func (self *Ethereum) StartAutoDAG() { + if self.autodagquit != nil { + return // already started + } + go func() { + glog.V(logger.Info).Infof("Automatic pregeneration of ethash DAG ON (ethash dir: %s)", ethash.DefaultDir) + var nextEpoch uint64 + timer := time.After(0) + self.autodagquit = make(chan bool) + for { + select { + case <-timer: + glog.V(logger.Info).Infof("checking DAG (ethash dir: %s)", ethash.DefaultDir) + currentBlock := self.ChainManager().CurrentBlock().NumberU64() + thisEpoch := currentBlock / epochLength + if nextEpoch <= thisEpoch { + if currentBlock%epochLength > autoDAGepochHeight { + if thisEpoch > 0 { + previousDag, previousDagFull := dagFiles(thisEpoch - 1) + os.Remove(filepath.Join(ethash.DefaultDir, previousDag)) + os.Remove(filepath.Join(ethash.DefaultDir, previousDagFull)) + glog.V(logger.Info).Infof("removed DAG for epoch %d (%s)", thisEpoch-1, previousDag) + } + nextEpoch = thisEpoch + 1 + dag, _ := dagFiles(nextEpoch) + if _, err := os.Stat(dag); os.IsNotExist(err) { + glog.V(logger.Info).Infof("Pregenerating DAG for epoch %d (%s)", nextEpoch, dag) + err := ethash.MakeDAG(nextEpoch*epochLength, "") // "" -> ethash.DefaultDir + if err != nil { + glog.V(logger.Error).Infof("Error generating DAG for epoch %d (%s)", nextEpoch, dag) + return + } + } else { + glog.V(logger.Error).Infof("DAG for epoch %d (%s)", nextEpoch, dag) + } + } + } + timer = time.After(autoDAGcheckInterval) + case <-self.autodagquit: + return + } + } + }() +} + +// dagFiles(epoch) returns the two alternative DAG filenames (not a path) +// 1) - 2) full-R- +func dagFiles(epoch uint64) (string, string) { + seedHash, _ := ethash.GetSeedHash(epoch * epochLength) + dag := fmt.Sprintf("full-R%d-%x", ethashRevision, seedHash[:8]) + return dag, "full-R" + dag +} + +// stopAutoDAG stops automatic DAG pregeneration by quitting the loop +func (self *Ethereum) StopAutoDAG() { + if self.autodagquit != nil { + close(self.autodagquit) + self.autodagquit = nil + } + glog.V(logger.Info).Infof("Automatic pregeneration of ethash DAG OFF (ethash dir: %s)", ethash.DefaultDir) +} + func saveProtocolVersion(db common.Database, protov int) { d, _ := db.Get([]byte("ProtocolVersion")) protocolVersion := common.NewValue(d).Uint()