aboutsummaryrefslogtreecommitdiffstats
path: root/eth
diff options
context:
space:
mode:
authorFelix Lange <fjl@twurst.com>2015-10-27 04:42:24 +0800
committerFelix Lange <fjl@twurst.com>2015-10-28 01:43:47 +0800
commitae1b5b3ff2611af1232643d38e13a77d704dae28 (patch)
tree2f575d95db4fe0e6f02308a21928f5fe2ed6e268 /eth
parent77878f76a935061cee82ae9c2a1bc64b192b592b (diff)
downloadgo-tangerine-ae1b5b3ff2611af1232643d38e13a77d704dae28.tar.gz
go-tangerine-ae1b5b3ff2611af1232643d38e13a77d704dae28.tar.zst
go-tangerine-ae1b5b3ff2611af1232643d38e13a77d704dae28.zip
eth, xeth: fix GasPriceOracle goroutine leak
XEth.gpo was being initialized as needed. WithState copies the XEth struct including the gpo field. If gpo was nil at the time of the copy and Call or Transact were invoked on it, an additional GPO listenLoop would be spawned. Move the lazy initialization to GasPriceOracle instead so the same GPO instance is shared among all created XEths. Fixes #1317 Might help with #1930
Diffstat (limited to 'eth')
-rw-r--r--eth/gasprice.go107
1 files changed, 58 insertions, 49 deletions
diff --git a/eth/gasprice.go b/eth/gasprice.go
index b4409f346..b752c22dd 100644
--- a/eth/gasprice.go
+++ b/eth/gasprice.go
@@ -23,49 +23,66 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
)
-const gpoProcessPastBlocks = 100
+const (
+ gpoProcessPastBlocks = 100
+
+ // for testing
+ gpoDefaultBaseCorrectionFactor = 110
+ gpoDefaultMinGasPrice = 10000000000000
+)
type blockPriceInfo struct {
baseGasPrice *big.Int
}
+// GasPriceOracle recommends gas prices based on the content of recent
+// blocks.
type GasPriceOracle struct {
- eth *Ethereum
- chain *core.BlockChain
- events event.Subscription
+ eth *Ethereum
+ initOnce sync.Once
+ minPrice *big.Int
+ lastBaseMutex sync.Mutex
+ lastBase *big.Int
+
+ // state of listenLoop
blocks map[uint64]*blockPriceInfo
firstProcessed, lastProcessed uint64
- lastBaseMutex sync.Mutex
- lastBase, minBase *big.Int
+ minBase *big.Int
+}
+
+// NewGasPriceOracle returns a new oracle.
+func NewGasPriceOracle(eth *Ethereum) *GasPriceOracle {
+ minprice := eth.GpoMinGasPrice
+ if minprice == nil {
+ minprice = big.NewInt(gpoDefaultMinGasPrice)
+ }
+ minbase := new(big.Int).Mul(minprice, big.NewInt(100))
+ if eth.GpobaseCorrectionFactor > 0 {
+ minbase = minbase.Div(minbase, big.NewInt(int64(eth.GpobaseCorrectionFactor)))
+ }
+ return &GasPriceOracle{
+ eth: eth,
+ blocks: make(map[uint64]*blockPriceInfo),
+ minBase: minbase,
+ minPrice: minprice,
+ lastBase: minprice,
+ }
}
-func NewGasPriceOracle(eth *Ethereum) (self *GasPriceOracle) {
- self = &GasPriceOracle{}
- self.blocks = make(map[uint64]*blockPriceInfo)
- self.eth = eth
- self.chain = eth.blockchain
- self.events = eth.EventMux().Subscribe(
- core.ChainEvent{},
- core.ChainSplitEvent{},
- )
-
- minbase := new(big.Int).Mul(self.eth.GpoMinGasPrice, big.NewInt(100))
- minbase = minbase.Div(minbase, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
- self.minBase = minbase
-
- self.processPastBlocks()
- go self.listenLoop()
- return
+func (gpo *GasPriceOracle) init() {
+ gpo.initOnce.Do(func() {
+ gpo.processPastBlocks(gpo.eth.BlockChain())
+ go gpo.listenLoop()
+ })
}
-func (self *GasPriceOracle) processPastBlocks() {
+func (self *GasPriceOracle) processPastBlocks(chain *core.BlockChain) {
last := int64(-1)
- cblock := self.chain.CurrentBlock()
+ cblock := chain.CurrentBlock()
if cblock != nil {
last = int64(cblock.NumberU64())
}
@@ -75,7 +92,7 @@ func (self *GasPriceOracle) processPastBlocks() {
}
self.firstProcessed = uint64(first)
for i := first; i <= last; i++ {
- block := self.chain.GetBlockByNumber(uint64(i))
+ block := chain.GetBlockByNumber(uint64(i))
if block != nil {
self.processBlock(block)
}
@@ -84,9 +101,10 @@ func (self *GasPriceOracle) processPastBlocks() {
}
func (self *GasPriceOracle) listenLoop() {
- defer self.events.Unsubscribe()
+ events := self.eth.EventMux().Subscribe(core.ChainEvent{}, core.ChainSplitEvent{})
+ defer events.Unsubscribe()
- for event := range self.events.Chan() {
+ for event := range events.Chan() {
switch event := event.Data.(type) {
case core.ChainEvent:
self.processBlock(event.Block)
@@ -102,7 +120,7 @@ func (self *GasPriceOracle) processBlock(block *types.Block) {
self.lastProcessed = i
}
- lastBase := self.eth.GpoMinGasPrice
+ lastBase := self.minPrice
bpl := self.blocks[i-1]
if bpl != nil {
lastBase = bpl.baseGasPrice
@@ -176,28 +194,19 @@ func (self *GasPriceOracle) lowestPrice(block *types.Block) *big.Int {
return minPrice
}
+// SuggestPrice returns the recommended gas price.
func (self *GasPriceOracle) SuggestPrice() *big.Int {
+ self.init()
self.lastBaseMutex.Lock()
- base := self.lastBase
+ price := new(big.Int).Set(self.lastBase)
self.lastBaseMutex.Unlock()
- if base == nil {
- base = self.eth.GpoMinGasPrice
+ price.Mul(price, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
+ price.Div(price, big.NewInt(100))
+ if price.Cmp(self.minPrice) < 0 {
+ price.Set(self.minPrice)
+ } else if self.eth.GpoMaxGasPrice != nil && price.Cmp(self.eth.GpoMaxGasPrice) > 0 {
+ price.Set(self.eth.GpoMaxGasPrice)
}
- if base == nil {
- return big.NewInt(10000000000000) // apparently MinGasPrice is not initialized during some tests
- }
-
- baseCorr := new(big.Int).Mul(base, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
- baseCorr.Div(baseCorr, big.NewInt(100))
-
- if baseCorr.Cmp(self.eth.GpoMinGasPrice) < 0 {
- return self.eth.GpoMinGasPrice
- }
-
- if baseCorr.Cmp(self.eth.GpoMaxGasPrice) > 0 {
- return self.eth.GpoMaxGasPrice
- }
-
- return baseCorr
+ return price
}