perf: add cache for gas permium estimation

Signed-off-by: Jakub Sztandera <kubuxu@protocol.ai>
This commit is contained in:
Jakub Sztandera 2021-03-03 16:05:21 +01:00
parent 897c5c7d82
commit 5f672c2ed0
No known key found for this signature in database
GPG Key ID: 9A9AF56F8B3879BA
3 changed files with 68 additions and 27 deletions

View File

@ -329,6 +329,8 @@ var ChainNode = Options(
Override(new(storagemarket.StorageClientNode), storageadapter.NewClientNodeAdapter), Override(new(storagemarket.StorageClientNode), storageadapter.NewClientNodeAdapter),
Override(HandleMigrateClientFundsKey, modules.HandleMigrateClientFunds), Override(HandleMigrateClientFundsKey, modules.HandleMigrateClientFunds),
Override(new(*full.GasPriceCache), full.NewGasPriceCache),
// Lite node API // Lite node API
ApplyIf(isLiteNode, ApplyIf(isLiteNode,
Override(new(messagesigner.MpoolNonceAPI), From(new(modules.MpoolNonceAPI))), Override(new(messagesigner.MpoolNonceAPI), From(new(modules.MpoolNonceAPI))),

View File

@ -8,6 +8,7 @@ import (
"github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/actors/builtin/paych"
lru "github.com/hashicorp/golang-lru"
"go.uber.org/fx" "go.uber.org/fx"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@ -39,6 +40,8 @@ type GasModule struct {
Chain *store.ChainStore Chain *store.ChainStore
Mpool *messagepool.MessagePool Mpool *messagepool.MessagePool
GetMaxFee dtypes.DefaultMaxFeeFunc GetMaxFee dtypes.DefaultMaxFeeFunc
PriceCache *GasPriceCache
} }
var _ GasModuleAPI = (*GasModule)(nil) var _ GasModuleAPI = (*GasModule)(nil)
@ -51,6 +54,53 @@ type GasAPI struct {
Stmgr *stmgr.StateManager Stmgr *stmgr.StateManager
Chain *store.ChainStore Chain *store.ChainStore
Mpool *messagepool.MessagePool Mpool *messagepool.MessagePool
PriceCache *GasPriceCache
}
func NewGasPriceCache() *GasPriceCache {
// 50 because we usually won't access more than 40
c, err := lru.New2Q(50)
if err != nil {
// err only if parameter is bad
panic(err)
}
return &GasPriceCache{
c: c,
}
}
type GasPriceCache struct {
c *lru.TwoQueueCache
}
type GasMeta struct {
Price big.Int
Limit int64
}
func (g *GasPriceCache) GetTSGasStats(cstore *store.ChainStore, ts *types.TipSet) ([]GasMeta, error) {
i, has := g.c.Get(ts.Key())
if has {
return i.([]GasMeta), nil
}
var prices []GasMeta
msgs, err := cstore.MessagesForTipset(ts)
if err != nil {
return nil, xerrors.Errorf("loading messages: %w", err)
}
for _, msg := range msgs {
prices = append(prices, GasMeta{
Price: msg.VMMessage().GasPremium,
Limit: msg.VMMessage().GasLimit,
})
}
g.c.Add(ts.Key(), prices)
return prices, nil
} }
const MinGasPremium = 100e3 const MinGasPremium = 100e3
@ -88,24 +138,19 @@ func gasEstimateFeeCap(cstore *store.ChainStore, msg *types.Message, maxqueueblk
return out, nil return out, nil
} }
type gasMeta struct {
price big.Int
limit int64
}
// finds 55th percntile instead of median to put negative pressure on gas price // finds 55th percntile instead of median to put negative pressure on gas price
func medianGasPremium(prices []gasMeta, blocks int) abi.TokenAmount { func medianGasPremium(prices []GasMeta, blocks int) abi.TokenAmount {
sort.Slice(prices, func(i, j int) bool { sort.Slice(prices, func(i, j int) bool {
// sort desc by price // sort desc by price
return prices[i].price.GreaterThan(prices[j].price) return prices[i].Price.GreaterThan(prices[j].Price)
}) })
at := build.BlockGasTarget * int64(blocks) / 2 // 50th at := build.BlockGasTarget * int64(blocks) / 2 // 50th
at += build.BlockGasTarget * int64(blocks) / (2 * 20) // move 5% further at += build.BlockGasTarget * int64(blocks) / (2 * 20) // move 5% further
prev1, prev2 := big.Zero(), big.Zero() prev1, prev2 := big.Zero(), big.Zero()
for _, price := range prices { for _, price := range prices {
prev1, prev2 = price.price, prev1 prev1, prev2 = price.Price, prev1
at -= price.limit at -= price.Limit
if at < 0 { if at < 0 {
break break
} }
@ -126,7 +171,7 @@ func (a *GasAPI) GasEstimateGasPremium(
gaslimit int64, gaslimit int64,
_ types.TipSetKey, _ types.TipSetKey,
) (types.BigInt, error) { ) (types.BigInt, error) {
return gasEstimateGasPremium(a.Chain, nblocksincl) return gasEstimateGasPremium(a.Chain, a.PriceCache, nblocksincl)
} }
func (m *GasModule) GasEstimateGasPremium( func (m *GasModule) GasEstimateGasPremium(
ctx context.Context, ctx context.Context,
@ -135,14 +180,14 @@ func (m *GasModule) GasEstimateGasPremium(
gaslimit int64, gaslimit int64,
_ types.TipSetKey, _ types.TipSetKey,
) (types.BigInt, error) { ) (types.BigInt, error) {
return gasEstimateGasPremium(m.Chain, nblocksincl) return gasEstimateGasPremium(m.Chain, m.PriceCache, nblocksincl)
} }
func gasEstimateGasPremium(cstore *store.ChainStore, nblocksincl uint64) (types.BigInt, error) { func gasEstimateGasPremium(cstore *store.ChainStore, cache *GasPriceCache, nblocksincl uint64) (types.BigInt, error) {
if nblocksincl == 0 { if nblocksincl == 0 {
nblocksincl = 1 nblocksincl = 1
} }
var prices []gasMeta var prices []GasMeta
var blocks int var blocks int
ts := cstore.GetHeaviestTipSet() ts := cstore.GetHeaviestTipSet()
@ -157,17 +202,11 @@ func gasEstimateGasPremium(cstore *store.ChainStore, nblocksincl uint64) (types.
} }
blocks += len(pts.Blocks()) blocks += len(pts.Blocks())
meta, err := cache.GetTSGasStats(cstore, pts)
msgs, err := cstore.MessagesForTipset(pts)
if err != nil { if err != nil {
return types.BigInt{}, xerrors.Errorf("loading messages: %w", err) return types.BigInt{}, err
}
for _, msg := range msgs {
prices = append(prices, gasMeta{
price: msg.VMMessage().GasPremium,
limit: msg.VMMessage().GasLimit,
})
} }
prices = append(prices, meta...)
ts = pts ts = pts
} }

View File

@ -12,27 +12,27 @@ import (
) )
func TestMedian(t *testing.T) { func TestMedian(t *testing.T) {
require.Equal(t, types.NewInt(5), medianGasPremium([]gasMeta{ require.Equal(t, types.NewInt(5), medianGasPremium([]GasMeta{
{big.NewInt(5), build.BlockGasTarget}, {big.NewInt(5), build.BlockGasTarget},
}, 1)) }, 1))
require.Equal(t, types.NewInt(10), medianGasPremium([]gasMeta{ require.Equal(t, types.NewInt(10), medianGasPremium([]GasMeta{
{big.NewInt(5), build.BlockGasTarget}, {big.NewInt(5), build.BlockGasTarget},
{big.NewInt(10), build.BlockGasTarget}, {big.NewInt(10), build.BlockGasTarget},
}, 1)) }, 1))
require.Equal(t, types.NewInt(15), medianGasPremium([]gasMeta{ require.Equal(t, types.NewInt(15), medianGasPremium([]GasMeta{
{big.NewInt(10), build.BlockGasTarget / 2}, {big.NewInt(10), build.BlockGasTarget / 2},
{big.NewInt(20), build.BlockGasTarget / 2}, {big.NewInt(20), build.BlockGasTarget / 2},
}, 1)) }, 1))
require.Equal(t, types.NewInt(25), medianGasPremium([]gasMeta{ require.Equal(t, types.NewInt(25), medianGasPremium([]GasMeta{
{big.NewInt(10), build.BlockGasTarget / 2}, {big.NewInt(10), build.BlockGasTarget / 2},
{big.NewInt(20), build.BlockGasTarget / 2}, {big.NewInt(20), build.BlockGasTarget / 2},
{big.NewInt(30), build.BlockGasTarget / 2}, {big.NewInt(30), build.BlockGasTarget / 2},
}, 1)) }, 1))
require.Equal(t, types.NewInt(15), medianGasPremium([]gasMeta{ require.Equal(t, types.NewInt(15), medianGasPremium([]GasMeta{
{big.NewInt(10), build.BlockGasTarget / 2}, {big.NewInt(10), build.BlockGasTarget / 2},
{big.NewInt(20), build.BlockGasTarget / 2}, {big.NewInt(20), build.BlockGasTarget / 2},
{big.NewInt(30), build.BlockGasTarget / 2}, {big.NewInt(30), build.BlockGasTarget / 2},