diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e942d53c84..c4309f44b6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -147,12 +147,8 @@ func init() { utils.FakePoWFlag, utils.NoCompactionFlag, utils.SolcPathFlag, - utils.GpoMinGasPriceFlag, - utils.GpoMaxGasPriceFlag, - utils.GpoFullBlockRatioFlag, - utils.GpobaseStepDownFlag, - utils.GpobaseStepUpFlag, - utils.GpobaseCorrectionFactorFlag, + utils.GpoBlocksFlag, + utils.GpoPercentileFlag, utils.ExtraDataFlag, } app.Flags = append(app.Flags, debug.Flags...) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 74768f507c..491a4eb982 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -151,12 +151,8 @@ var AppHelpFlagGroups = []flagGroup{ { Name: "GAS PRICE ORACLE", Flags: []cli.Flag{ - utils.GpoMinGasPriceFlag, - utils.GpoMaxGasPriceFlag, - utils.GpoFullBlockRatioFlag, - utils.GpobaseStepDownFlag, - utils.GpobaseStepUpFlag, - utils.GpobaseCorrectionFactorFlag, + utils.GpoBlocksFlag, + utils.GpoPercentileFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index acdf5d5dcb..0ca407a753 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -409,35 +409,15 @@ var ( } // Gas price oracle settings - GpoMinGasPriceFlag = BigFlag{ - Name: "gpomin", - Usage: "Minimum suggested gas price", - Value: big.NewInt(20 * params.Shannon), - } - GpoMaxGasPriceFlag = BigFlag{ - Name: "gpomax", - Usage: "Maximum suggested gas price", - Value: big.NewInt(500 * params.Shannon), - } - GpoFullBlockRatioFlag = cli.IntFlag{ - Name: "gpofull", - Usage: "Full block threshold for gas price calculation (%)", - Value: 80, - } - GpobaseStepDownFlag = cli.IntFlag{ - Name: "gpobasedown", - Usage: "Suggested gas price base step down ratio (1/1000)", + GpoBlocksFlag = cli.IntFlag{ + Name: "gpoblocks", + Usage: "Number of recent blocks to check for gas prices", Value: 10, } - GpobaseStepUpFlag = cli.IntFlag{ - Name: "gpobaseup", - Usage: "Suggested gas price base step up ratio (1/1000)", - Value: 100, - } - GpobaseCorrectionFactorFlag = cli.IntFlag{ - Name: "gpobasecf", - Usage: "Suggested gas price base correction factor (%)", - Value: 110, + GpoPercentileFlag = cli.IntFlag{ + Name: "gpopercentile", + Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices", + Value: 50, } ) @@ -798,12 +778,8 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) { ExtraData: MakeMinerExtra(extra, ctx), DocRoot: ctx.GlobalString(DocRootFlag.Name), GasPrice: GlobalBig(ctx, GasPriceFlag.Name), - GpoMinGasPrice: GlobalBig(ctx, GpoMinGasPriceFlag.Name), - GpoMaxGasPrice: GlobalBig(ctx, GpoMaxGasPriceFlag.Name), - GpoFullBlockRatio: ctx.GlobalInt(GpoFullBlockRatioFlag.Name), - GpobaseStepDown: ctx.GlobalInt(GpobaseStepDownFlag.Name), - GpobaseStepUp: ctx.GlobalInt(GpobaseStepUpFlag.Name), - GpobaseCorrectionFactor: ctx.GlobalInt(GpobaseCorrectionFactorFlag.Name), + GpoBlocks: ctx.GlobalInt(GpoBlocksFlag.Name), + GpoPercentile: ctx.GlobalInt(GpoPercentileFlag.Name), SolcPath: ctx.GlobalString(SolcPathFlag.Name), EthashCacheDir: MakeEthashCacheDir(ctx), EthashCachesInMem: ctx.GlobalInt(EthashCachesInMemoryFlag.Name), diff --git a/eth/api_backend.go b/eth/api_backend.go index 418a344354..61e1844017 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -39,7 +39,7 @@ import ( // EthApiBackend implements ethapi.Backend for full nodes type EthApiBackend struct { eth *Ethereum - gpo *gasprice.GasPriceOracle + gpo *gasprice.Oracle } func (b *EthApiBackend) ChainConfig() *params.ChainConfig { @@ -186,7 +186,7 @@ func (b *EthApiBackend) ProtocolVersion() int { } func (b *EthApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { - return b.gpo.SuggestPrice(), nil + return b.gpo.SuggestPrice(ctx) } func (b *EthApiBackend) ChainDb() ethdb.Database { diff --git a/eth/backend.go b/eth/backend.go index af1d46a2cc..f241d5f34f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -84,12 +84,8 @@ type Config struct { MinerThreads int SolcPath string - GpoMinGasPrice *big.Int - GpoMaxGasPrice *big.Int - GpoFullBlockRatio int - GpobaseStepDown int - GpobaseStepUp int - GpobaseCorrectionFactor int + GpoBlocks int + GpoPercentile int EnablePreimageRecording bool } @@ -211,16 +207,13 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth.miner.SetGasPrice(config.GasPrice) eth.miner.SetExtra(config.ExtraData) - gpoParams := &gasprice.GpoParams{ - GpoMinGasPrice: config.GpoMinGasPrice, - GpoMaxGasPrice: config.GpoMaxGasPrice, - GpoFullBlockRatio: config.GpoFullBlockRatio, - GpobaseStepDown: config.GpobaseStepDown, - GpobaseStepUp: config.GpobaseStepUp, - GpobaseCorrectionFactor: config.GpobaseCorrectionFactor, + eth.ApiBackend = &EthApiBackend{eth, nil} + gpoParams := gasprice.Config{ + Blocks: config.GpoBlocks, + Percentile: config.GpoPercentile, + Default: config.GasPrice, } - gpo := gasprice.NewGasPriceOracle(eth.blockchain, chainDb, eth.eventMux, gpoParams) - eth.ApiBackend = &EthApiBackend{eth, gpo} + eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams) return eth, nil } diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 73951bce90..bac048c88a 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -1,4 +1,4 @@ -// Copyright 2015 The go-ethereum Authors +// Copyright 2016 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -17,212 +17,158 @@ package gasprice import ( + "context" "math/big" - "math/rand" + "sort" "sync" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" ) -const ( - gpoProcessPastBlocks = 100 +var maxPrice = big.NewInt(500 * params.Shannon) - // for testing - gpoDefaultBaseCorrectionFactor = 110 - gpoDefaultMinGasPrice = 10000000000000 -) - -type blockPriceInfo struct { - baseGasPrice *big.Int -} - -type GpoParams struct { - GpoMinGasPrice *big.Int - GpoMaxGasPrice *big.Int - GpoFullBlockRatio int - GpobaseStepDown int - GpobaseStepUp int - GpobaseCorrectionFactor int +type Config struct { + Blocks int + Percentile int + Default *big.Int } -// GasPriceOracle recommends gas prices based on the content of recent -// blocks. -type GasPriceOracle struct { - chain *core.BlockChain - db ethdb.Database - evmux *event.TypeMux - params *GpoParams - initOnce sync.Once - minPrice *big.Int - lastBaseMutex sync.Mutex - lastBase *big.Int - - // state of listenLoop - blocks map[uint64]*blockPriceInfo - firstProcessed, lastProcessed uint64 - minBase *big.Int +// Oracle recommends gas prices based on the content of recent +// blocks. Suitable for both light and full clients. +type Oracle struct { + backend ethapi.Backend + lastHead common.Hash + lastPrice *big.Int + cacheLock sync.RWMutex + fetchLock sync.Mutex + + checkBlocks, maxEmpty, maxBlocks int + percentile int } -// NewGasPriceOracle returns a new oracle. -func NewGasPriceOracle(chain *core.BlockChain, db ethdb.Database, evmux *event.TypeMux, params *GpoParams) *GasPriceOracle { - minprice := params.GpoMinGasPrice - if minprice == nil { - minprice = big.NewInt(gpoDefaultMinGasPrice) +// NewOracle returns a new oracle. +func NewOracle(backend ethapi.Backend, params Config) *Oracle { + blocks := params.Blocks + if blocks < 1 { + blocks = 1 } - minbase := new(big.Int).Mul(minprice, big.NewInt(100)) - if params.GpobaseCorrectionFactor > 0 { - minbase = minbase.Div(minbase, big.NewInt(int64(params.GpobaseCorrectionFactor))) + percent := params.Percentile + if percent < 0 { + percent = 0 } - return &GasPriceOracle{ - chain: chain, - db: db, - evmux: evmux, - params: params, - blocks: make(map[uint64]*blockPriceInfo), - minBase: minbase, - minPrice: minprice, - lastBase: minprice, + if percent > 100 { + percent = 100 } -} - -func (gpo *GasPriceOracle) init() { - gpo.initOnce.Do(func() { - gpo.processPastBlocks() - go gpo.listenLoop() - }) -} - -func (self *GasPriceOracle) processPastBlocks() { - last := int64(-1) - cblock := self.chain.CurrentBlock() - if cblock != nil { - last = int64(cblock.NumberU64()) - } - first := int64(0) - if last > gpoProcessPastBlocks { - first = last - gpoProcessPastBlocks - } - self.firstProcessed = uint64(first) - for i := first; i <= last; i++ { - block := self.chain.GetBlockByNumber(uint64(i)) - if block != nil { - self.processBlock(block) - } + return &Oracle{ + backend: backend, + lastPrice: params.Default, + checkBlocks: blocks, + maxEmpty: blocks / 2, + maxBlocks: blocks * 5, + percentile: percent, } - } -func (self *GasPriceOracle) listenLoop() { - events := self.evmux.Subscribe(core.ChainEvent{}, core.ChainSplitEvent{}) - defer events.Unsubscribe() - - for event := range events.Chan() { - switch event := event.Data.(type) { - case core.ChainEvent: - self.processBlock(event.Block) - case core.ChainSplitEvent: - self.processBlock(event.Block) +// SuggestPrice returns the recommended gas price. +func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { + gpo.cacheLock.RLock() + lastHead := gpo.lastHead + lastPrice := gpo.lastPrice + gpo.cacheLock.RUnlock() + + head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + headHash := head.Hash() + if headHash == lastHead { + return lastPrice, nil + } + + gpo.fetchLock.Lock() + defer gpo.fetchLock.Unlock() + + // try checking the cache again, maybe the last fetch fetched what we need + gpo.cacheLock.RLock() + lastHead = gpo.lastHead + lastPrice = gpo.lastPrice + gpo.cacheLock.RUnlock() + if headHash == lastHead { + return lastPrice, nil + } + + blockNum := head.Number.Uint64() + ch := make(chan getBlockPricesResult, gpo.checkBlocks) + sent := 0 + exp := 0 + var txPrices []*big.Int + for sent < gpo.checkBlocks && blockNum > 0 { + go gpo.getBlockPrices(ctx, blockNum, ch) + sent++ + exp++ + blockNum-- + } + maxEmpty := gpo.maxEmpty + for exp > 0 { + res := <-ch + if res.err != nil { + return lastPrice, res.err + } + exp-- + if len(res.prices) > 0 { + txPrices = append(txPrices, res.prices...) + continue + } + if maxEmpty > 0 { + maxEmpty-- + continue + } + if blockNum > 0 && sent < gpo.maxBlocks { + go gpo.getBlockPrices(ctx, blockNum, ch) + sent++ + exp++ + blockNum-- } } -} - -func (self *GasPriceOracle) processBlock(block *types.Block) { - i := block.NumberU64() - if i > self.lastProcessed { - self.lastProcessed = i - } - - lastBase := self.minPrice - bpl := self.blocks[i-1] - if bpl != nil { - lastBase = bpl.baseGasPrice - } - if lastBase == nil { - return - } - - var corr int - lp := self.lowestPrice(block) - if lp == nil { - return - } - - if lastBase.Cmp(lp) < 0 { - corr = self.params.GpobaseStepUp - } else { - corr = -self.params.GpobaseStepDown - } - - crand := int64(corr * (900 + rand.Intn(201))) - newBase := new(big.Int).Mul(lastBase, big.NewInt(1000000+crand)) - newBase.Div(newBase, big.NewInt(1000000)) - - if newBase.Cmp(self.minBase) < 0 { - newBase = self.minBase + price := lastPrice + if len(txPrices) > 0 { + sort.Sort(bigIntArray(txPrices)) + price = txPrices[(len(txPrices)-1)*gpo.percentile/100] } - - bpi := self.blocks[i] - if bpi == nil { - bpi = &blockPriceInfo{} - self.blocks[i] = bpi + if price.Cmp(maxPrice) > 0 { + price = new(big.Int).Set(maxPrice) } - bpi.baseGasPrice = newBase - self.lastBaseMutex.Lock() - self.lastBase = newBase - self.lastBaseMutex.Unlock() - log.Trace("Processed block, base price updated", "number", i, "base", newBase) + gpo.cacheLock.Lock() + gpo.lastHead = headHash + gpo.lastPrice = price + gpo.cacheLock.Unlock() + return price, nil } -// returns the lowers possible price with which a tx was or could have been included -func (self *GasPriceOracle) lowestPrice(block *types.Block) *big.Int { - gasUsed := big.NewInt(0) - - receipts := core.GetBlockReceipts(self.db, block.Hash(), block.NumberU64()) - if len(receipts) > 0 { - if cgu := receipts[len(receipts)-1].CumulativeGasUsed; cgu != nil { - gasUsed = receipts[len(receipts)-1].CumulativeGasUsed - } - } +type getBlockPricesResult struct { + prices []*big.Int + err error +} - if new(big.Int).Mul(gasUsed, big.NewInt(100)).Cmp(new(big.Int).Mul(block.GasLimit(), - big.NewInt(int64(self.params.GpoFullBlockRatio)))) < 0 { - // block is not full, could have posted a tx with MinGasPrice - return big.NewInt(0) +// getLowestPrice calculates the lowest transaction gas price in a given block +// and sends it to the result channel. If the block is empty, price is nil. +func (gpo *Oracle) getBlockPrices(ctx context.Context, blockNum uint64, ch chan getBlockPricesResult) { + block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) + if block == nil { + ch <- getBlockPricesResult{nil, err} + return } - txs := block.Transactions() - if len(txs) == 0 { - return big.NewInt(0) + prices := make([]*big.Int, len(txs)) + for i, tx := range txs { + prices[i] = tx.GasPrice() } - // block is full, find smallest gasPrice - minPrice := txs[0].GasPrice() - for i := 1; i < len(txs); i++ { - price := txs[i].GasPrice() - if price.Cmp(minPrice) < 0 { - minPrice = price - } - } - return minPrice + ch <- getBlockPricesResult{prices, nil} } -// SuggestPrice returns the recommended gas price. -func (self *GasPriceOracle) SuggestPrice() *big.Int { - self.init() - self.lastBaseMutex.Lock() - price := new(big.Int).Set(self.lastBase) - self.lastBaseMutex.Unlock() - - price.Mul(price, big.NewInt(int64(self.params.GpobaseCorrectionFactor))) - price.Div(price, big.NewInt(100)) - if price.Cmp(self.minPrice) < 0 { - price.Set(self.minPrice) - } else if self.params.GpoMaxGasPrice != nil && price.Cmp(self.params.GpoMaxGasPrice) > 0 { - price.Set(self.params.GpoMaxGasPrice) - } - return price -} +type bigIntArray []*big.Int + +func (s bigIntArray) Len() int { return len(s) } +func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } +func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] } diff --git a/eth/gasprice/lightprice.go b/eth/gasprice/lightprice.go deleted file mode 100644 index 562c7dd974..0000000000 --- a/eth/gasprice/lightprice.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package gasprice - -import ( - "context" - "math/big" - "sort" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/rpc" -) - -const ( - LpoAvgCount = 5 - LpoMinCount = 3 - LpoMaxBlocks = 20 - LpoSelect = 50 - LpoDefaultPrice = 20000000000 -) - -// LightPriceOracle recommends gas prices based on the content of recent -// blocks. Suitable for both light and full clients. -type LightPriceOracle struct { - backend ethapi.Backend - lastHead common.Hash - lastPrice *big.Int - cacheLock sync.RWMutex - fetchLock sync.Mutex -} - -// NewLightPriceOracle returns a new oracle. -func NewLightPriceOracle(backend ethapi.Backend) *LightPriceOracle { - return &LightPriceOracle{ - backend: backend, - lastPrice: big.NewInt(LpoDefaultPrice), - } -} - -// SuggestPrice returns the recommended gas price. -func (self *LightPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error) { - self.cacheLock.RLock() - lastHead := self.lastHead - lastPrice := self.lastPrice - self.cacheLock.RUnlock() - - head, _ := self.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) - headHash := head.Hash() - if headHash == lastHead { - return lastPrice, nil - } - - self.fetchLock.Lock() - defer self.fetchLock.Unlock() - - // try checking the cache again, maybe the last fetch fetched what we need - self.cacheLock.RLock() - lastHead = self.lastHead - lastPrice = self.lastPrice - self.cacheLock.RUnlock() - if headHash == lastHead { - return lastPrice, nil - } - - blockNum := head.Number.Uint64() - chn := make(chan lpResult, LpoMaxBlocks) - sent := 0 - exp := 0 - var lps bigIntArray - for sent < LpoAvgCount && blockNum > 0 { - go self.getLowestPrice(ctx, blockNum, chn) - sent++ - exp++ - blockNum-- - } - maxEmpty := LpoAvgCount - LpoMinCount - for exp > 0 { - res := <-chn - if res.err != nil { - return nil, res.err - } - exp-- - if res.price != nil { - lps = append(lps, res.price) - } else { - if maxEmpty > 0 { - maxEmpty-- - } else { - if blockNum > 0 && sent < LpoMaxBlocks { - go self.getLowestPrice(ctx, blockNum, chn) - sent++ - exp++ - blockNum-- - } - } - } - } - price := lastPrice - if len(lps) > 0 { - sort.Sort(lps) - price = lps[(len(lps)-1)*LpoSelect/100] - } - - self.cacheLock.Lock() - self.lastHead = headHash - self.lastPrice = price - self.cacheLock.Unlock() - return price, nil -} - -type lpResult struct { - price *big.Int - err error -} - -// getLowestPrice calculates the lowest transaction gas price in a given block -// and sends it to the result channel. If the block is empty, price is nil. -func (self *LightPriceOracle) getLowestPrice(ctx context.Context, blockNum uint64, chn chan lpResult) { - block, err := self.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) - if block == nil { - chn <- lpResult{nil, err} - return - } - txs := block.Transactions() - if len(txs) == 0 { - chn <- lpResult{nil, nil} - return - } - // find smallest gasPrice - minPrice := txs[0].GasPrice() - for i := 1; i < len(txs); i++ { - price := txs[i].GasPrice() - if price.Cmp(minPrice) < 0 { - minPrice = price - } - } - chn <- lpResult{minPrice, nil} -} - -type bigIntArray []*big.Int - -func (s bigIntArray) Len() int { return len(s) } -func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } -func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] } diff --git a/les/api_backend.go b/les/api_backend.go index df2782f780..67de3bcd5c 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -38,7 +38,7 @@ import ( type LesApiBackend struct { eth *LightEthereum - gpo *gasprice.LightPriceOracle + gpo *gasprice.Oracle } func (b *LesApiBackend) ChainConfig() *params.ChainConfig { diff --git a/les/backend.go b/les/backend.go index bb08efd915..3aad16fa0e 100644 --- a/les/backend.go +++ b/les/backend.go @@ -111,7 +111,12 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { relay.reqDist = eth.protocolManager.reqDist eth.ApiBackend = &LesApiBackend{eth, nil} - eth.ApiBackend.gpo = gasprice.NewLightPriceOracle(eth.ApiBackend) + gpoParams := gasprice.Config{ + Blocks: config.GpoBlocks, + Percentile: config.GpoPercentile, + Default: config.GasPrice, + } + eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams) return eth, nil } diff --git a/mobile/geth.go b/mobile/geth.go index e070cec56d..402f0b6801 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -168,20 +168,16 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { // Register the Ethereum protocol if requested if config.EthereumEnabled { ethConf := ð.Config{ - Genesis: genesis, - LightMode: true, - DatabaseCache: config.EthereumDatabaseCache, - NetworkId: config.EthereumNetworkID, - GasPrice: new(big.Int).SetUint64(20 * params.Shannon), - GpoMinGasPrice: new(big.Int).SetUint64(50 * params.Shannon), - GpoMaxGasPrice: new(big.Int).SetUint64(500 * params.Shannon), - GpoFullBlockRatio: 80, - GpobaseStepDown: 10, - GpobaseStepUp: 100, - GpobaseCorrectionFactor: 110, - EthashCacheDir: "ethash", - EthashCachesInMem: 2, - EthashCachesOnDisk: 3, + Genesis: genesis, + LightMode: true, + DatabaseCache: config.EthereumDatabaseCache, + NetworkId: config.EthereumNetworkID, + GasPrice: new(big.Int).SetUint64(20 * params.Shannon), + GpoBlocks: 5, + GpoPercentile: 50, + EthashCacheDir: "ethash", + EthashCachesInMem: 2, + EthashCachesOnDisk: 3, } if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return les.New(ctx, ethConf)