cmd, les, eth, eth/gasprice: using new gas price oracle (#13853)

* cmd, les, eth, eth/gasprice: using new gas price oracle

* eth/gasprice: renamed source file

* eth/gasprice: added security checks for gpo params

* eth/gasprice: fixed naming issues

* eth/gasprice: max limit, maxEmpty
This commit is contained in:
Felföldi Zsolt 2017-04-06 16:20:42 +02:00 committed by Péter Szilágyi
parent 0ec1104ba9
commit 9aca9e6deb
10 changed files with 176 additions and 428 deletions

@ -147,12 +147,8 @@ func init() {
utils.FakePoWFlag, utils.FakePoWFlag,
utils.NoCompactionFlag, utils.NoCompactionFlag,
utils.SolcPathFlag, utils.SolcPathFlag,
utils.GpoMinGasPriceFlag, utils.GpoBlocksFlag,
utils.GpoMaxGasPriceFlag, utils.GpoPercentileFlag,
utils.GpoFullBlockRatioFlag,
utils.GpobaseStepDownFlag,
utils.GpobaseStepUpFlag,
utils.GpobaseCorrectionFactorFlag,
utils.ExtraDataFlag, utils.ExtraDataFlag,
} }
app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, debug.Flags...)

@ -151,12 +151,8 @@ var AppHelpFlagGroups = []flagGroup{
{ {
Name: "GAS PRICE ORACLE", Name: "GAS PRICE ORACLE",
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.GpoMinGasPriceFlag, utils.GpoBlocksFlag,
utils.GpoMaxGasPriceFlag, utils.GpoPercentileFlag,
utils.GpoFullBlockRatioFlag,
utils.GpobaseStepDownFlag,
utils.GpobaseStepUpFlag,
utils.GpobaseCorrectionFactorFlag,
}, },
}, },
{ {

@ -409,35 +409,15 @@ var (
} }
// Gas price oracle settings // Gas price oracle settings
GpoMinGasPriceFlag = BigFlag{ GpoBlocksFlag = cli.IntFlag{
Name: "gpomin", Name: "gpoblocks",
Usage: "Minimum suggested gas price", Usage: "Number of recent blocks to check for gas prices",
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)",
Value: 10, Value: 10,
} }
GpobaseStepUpFlag = cli.IntFlag{ GpoPercentileFlag = cli.IntFlag{
Name: "gpobaseup", Name: "gpopercentile",
Usage: "Suggested gas price base step up ratio (1/1000)", Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices",
Value: 100, Value: 50,
}
GpobaseCorrectionFactorFlag = cli.IntFlag{
Name: "gpobasecf",
Usage: "Suggested gas price base correction factor (%)",
Value: 110,
} }
) )
@ -798,12 +778,8 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
ExtraData: MakeMinerExtra(extra, ctx), ExtraData: MakeMinerExtra(extra, ctx),
DocRoot: ctx.GlobalString(DocRootFlag.Name), DocRoot: ctx.GlobalString(DocRootFlag.Name),
GasPrice: GlobalBig(ctx, GasPriceFlag.Name), GasPrice: GlobalBig(ctx, GasPriceFlag.Name),
GpoMinGasPrice: GlobalBig(ctx, GpoMinGasPriceFlag.Name), GpoBlocks: ctx.GlobalInt(GpoBlocksFlag.Name),
GpoMaxGasPrice: GlobalBig(ctx, GpoMaxGasPriceFlag.Name), GpoPercentile: ctx.GlobalInt(GpoPercentileFlag.Name),
GpoFullBlockRatio: ctx.GlobalInt(GpoFullBlockRatioFlag.Name),
GpobaseStepDown: ctx.GlobalInt(GpobaseStepDownFlag.Name),
GpobaseStepUp: ctx.GlobalInt(GpobaseStepUpFlag.Name),
GpobaseCorrectionFactor: ctx.GlobalInt(GpobaseCorrectionFactorFlag.Name),
SolcPath: ctx.GlobalString(SolcPathFlag.Name), SolcPath: ctx.GlobalString(SolcPathFlag.Name),
EthashCacheDir: MakeEthashCacheDir(ctx), EthashCacheDir: MakeEthashCacheDir(ctx),
EthashCachesInMem: ctx.GlobalInt(EthashCachesInMemoryFlag.Name), EthashCachesInMem: ctx.GlobalInt(EthashCachesInMemoryFlag.Name),

@ -39,7 +39,7 @@ import (
// EthApiBackend implements ethapi.Backend for full nodes // EthApiBackend implements ethapi.Backend for full nodes
type EthApiBackend struct { type EthApiBackend struct {
eth *Ethereum eth *Ethereum
gpo *gasprice.GasPriceOracle gpo *gasprice.Oracle
} }
func (b *EthApiBackend) ChainConfig() *params.ChainConfig { 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) { 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 { func (b *EthApiBackend) ChainDb() ethdb.Database {

@ -84,12 +84,8 @@ type Config struct {
MinerThreads int MinerThreads int
SolcPath string SolcPath string
GpoMinGasPrice *big.Int GpoBlocks int
GpoMaxGasPrice *big.Int GpoPercentile int
GpoFullBlockRatio int
GpobaseStepDown int
GpobaseStepUp int
GpobaseCorrectionFactor int
EnablePreimageRecording bool EnablePreimageRecording bool
} }
@ -211,16 +207,13 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth.miner.SetGasPrice(config.GasPrice) eth.miner.SetGasPrice(config.GasPrice)
eth.miner.SetExtra(config.ExtraData) eth.miner.SetExtra(config.ExtraData)
gpoParams := &gasprice.GpoParams{ eth.ApiBackend = &EthApiBackend{eth, nil}
GpoMinGasPrice: config.GpoMinGasPrice, gpoParams := gasprice.Config{
GpoMaxGasPrice: config.GpoMaxGasPrice, Blocks: config.GpoBlocks,
GpoFullBlockRatio: config.GpoFullBlockRatio, Percentile: config.GpoPercentile,
GpobaseStepDown: config.GpobaseStepDown, Default: config.GasPrice,
GpobaseStepUp: config.GpobaseStepUp,
GpobaseCorrectionFactor: config.GpobaseCorrectionFactor,
} }
gpo := gasprice.NewGasPriceOracle(eth.blockchain, chainDb, eth.eventMux, gpoParams) eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams)
eth.ApiBackend = &EthApiBackend{eth, gpo}
return eth, nil return eth, nil
} }

@ -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. // This file is part of the go-ethereum library.
// //
// The go-ethereum library is free software: you can redistribute it and/or modify // The go-ethereum library is free software: you can redistribute it and/or modify
@ -17,212 +17,158 @@
package gasprice package gasprice
import ( import (
"context"
"math/big" "math/big"
"math/rand" "sort"
"sync" "sync"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/log"
) )
const ( var maxPrice = big.NewInt(500 * params.Shannon)
gpoProcessPastBlocks = 100
// for testing type Config struct {
gpoDefaultBaseCorrectionFactor = 110 Blocks int
gpoDefaultMinGasPrice = 10000000000000 Percentile int
) Default *big.Int
type blockPriceInfo struct {
baseGasPrice *big.Int
} }
type GpoParams struct { // Oracle recommends gas prices based on the content of recent
GpoMinGasPrice *big.Int // blocks. Suitable for both light and full clients.
GpoMaxGasPrice *big.Int type Oracle struct {
GpoFullBlockRatio int backend ethapi.Backend
GpobaseStepDown int lastHead common.Hash
GpobaseStepUp int lastPrice *big.Int
GpobaseCorrectionFactor int cacheLock sync.RWMutex
fetchLock sync.Mutex
checkBlocks, maxEmpty, maxBlocks int
percentile int
} }
// GasPriceOracle recommends gas prices based on the content of recent // NewOracle returns a new oracle.
// blocks. func NewOracle(backend ethapi.Backend, params Config) *Oracle {
type GasPriceOracle struct { blocks := params.Blocks
chain *core.BlockChain if blocks < 1 {
db ethdb.Database blocks = 1
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
} }
percent := params.Percentile
// NewGasPriceOracle returns a new oracle. if percent < 0 {
func NewGasPriceOracle(chain *core.BlockChain, db ethdb.Database, evmux *event.TypeMux, params *GpoParams) *GasPriceOracle { percent = 0
minprice := params.GpoMinGasPrice
if minprice == nil {
minprice = big.NewInt(gpoDefaultMinGasPrice)
} }
minbase := new(big.Int).Mul(minprice, big.NewInt(100)) if percent > 100 {
if params.GpobaseCorrectionFactor > 0 { percent = 100
minbase = minbase.Div(minbase, big.NewInt(int64(params.GpobaseCorrectionFactor)))
} }
return &GasPriceOracle{ return &Oracle{
chain: chain, backend: backend,
db: db, lastPrice: params.Default,
evmux: evmux, checkBlocks: blocks,
params: params, maxEmpty: blocks / 2,
blocks: make(map[uint64]*blockPriceInfo), maxBlocks: blocks * 5,
minBase: minbase, percentile: percent,
minPrice: minprice,
lastBase: minprice,
} }
} }
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)
}
}
}
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)
}
}
}
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
}
bpi := self.blocks[i]
if bpi == nil {
bpi = &blockPriceInfo{}
self.blocks[i] = bpi
}
bpi.baseGasPrice = newBase
self.lastBaseMutex.Lock()
self.lastBase = newBase
self.lastBaseMutex.Unlock()
log.Trace("Processed block, base price updated", "number", i, "base", newBase)
}
// 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
}
}
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)
}
txs := block.Transactions()
if len(txs) == 0 {
return big.NewInt(0)
}
// 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
}
// SuggestPrice returns the recommended gas price. // SuggestPrice returns the recommended gas price.
func (self *GasPriceOracle) SuggestPrice() *big.Int { func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
self.init() gpo.cacheLock.RLock()
self.lastBaseMutex.Lock() lastHead := gpo.lastHead
price := new(big.Int).Set(self.lastBase) lastPrice := gpo.lastPrice
self.lastBaseMutex.Unlock() gpo.cacheLock.RUnlock()
price.Mul(price, big.NewInt(int64(self.params.GpobaseCorrectionFactor))) head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
price.Div(price, big.NewInt(100)) headHash := head.Hash()
if price.Cmp(self.minPrice) < 0 { if headHash == lastHead {
price.Set(self.minPrice) return lastPrice, nil
} else if self.params.GpoMaxGasPrice != nil && price.Cmp(self.params.GpoMaxGasPrice) > 0 {
price.Set(self.params.GpoMaxGasPrice)
} }
return price
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--
}
}
price := lastPrice
if len(txPrices) > 0 {
sort.Sort(bigIntArray(txPrices))
price = txPrices[(len(txPrices)-1)*gpo.percentile/100]
}
if price.Cmp(maxPrice) > 0 {
price = new(big.Int).Set(maxPrice)
}
gpo.cacheLock.Lock()
gpo.lastHead = headHash
gpo.lastPrice = price
gpo.cacheLock.Unlock()
return price, nil
}
type getBlockPricesResult struct {
prices []*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 (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()
prices := make([]*big.Int, len(txs))
for i, tx := range txs {
prices[i] = tx.GasPrice()
}
ch <- getBlockPricesResult{prices, 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] }

@ -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 <http://www.gnu.org/licenses/>.
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] }

@ -38,7 +38,7 @@ import (
type LesApiBackend struct { type LesApiBackend struct {
eth *LightEthereum eth *LightEthereum
gpo *gasprice.LightPriceOracle gpo *gasprice.Oracle
} }
func (b *LesApiBackend) ChainConfig() *params.ChainConfig { func (b *LesApiBackend) ChainConfig() *params.ChainConfig {

@ -111,7 +111,12 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
relay.reqDist = eth.protocolManager.reqDist relay.reqDist = eth.protocolManager.reqDist
eth.ApiBackend = &LesApiBackend{eth, nil} 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 return eth, nil
} }

@ -173,12 +173,8 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
DatabaseCache: config.EthereumDatabaseCache, DatabaseCache: config.EthereumDatabaseCache,
NetworkId: config.EthereumNetworkID, NetworkId: config.EthereumNetworkID,
GasPrice: new(big.Int).SetUint64(20 * params.Shannon), GasPrice: new(big.Int).SetUint64(20 * params.Shannon),
GpoMinGasPrice: new(big.Int).SetUint64(50 * params.Shannon), GpoBlocks: 5,
GpoMaxGasPrice: new(big.Int).SetUint64(500 * params.Shannon), GpoPercentile: 50,
GpoFullBlockRatio: 80,
GpobaseStepDown: 10,
GpobaseStepUp: 100,
GpobaseCorrectionFactor: 110,
EthashCacheDir: "ethash", EthashCacheDir: "ethash",
EthashCachesInMem: 2, EthashCachesInMem: 2,
EthashCachesOnDisk: 3, EthashCachesOnDisk: 3,